mirror of
https://github.com/Noratrieb/terustform.git
synced 2026-01-14 16:35:11 +01:00
continue
This commit is contained in:
parent
bac720c512
commit
69daf83c53
7 changed files with 442 additions and 91 deletions
29
src/cert.rs
Normal file
29
src/cert.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use eyre::{Context, Result};
|
||||
use rcgen::{DistinguishedName, DnType, IsCa, KeyUsagePurpose};
|
||||
use time::Duration;
|
||||
|
||||
pub fn generate_cert() -> Result<(tonic::transport::Identity, rcgen::Certificate)> {
|
||||
// https://github.com/hashicorp/go-plugin/blob/8d2aaa458971cba97c3bfec1b0380322e024b514/mtls.go#L20
|
||||
let keypair = rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)
|
||||
.wrap_err("failed to generate keypair")?;
|
||||
|
||||
let mut params =
|
||||
rcgen::CertificateParams::new(["localhost".to_owned()]).wrap_err("creating cert params")?;
|
||||
params.key_usages = vec![
|
||||
KeyUsagePurpose::DigitalSignature,
|
||||
KeyUsagePurpose::KeyEncipherment,
|
||||
KeyUsagePurpose::KeyAgreement,
|
||||
KeyUsagePurpose::KeyCertSign,
|
||||
];
|
||||
params.is_ca = IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
|
||||
params.not_before = time::OffsetDateTime::now_utc().saturating_add(Duration::seconds(-30));
|
||||
params.not_after = time::OffsetDateTime::now_utc().saturating_add(Duration::seconds(262980));
|
||||
let mut dn = DistinguishedName::new();
|
||||
dn.push(DnType::OrganizationName, "HashiCorp");
|
||||
dn.push(DnType::CommonName, "localhost");
|
||||
params.distinguished_name = dn;
|
||||
|
||||
let cert = params.self_signed(&keypair).wrap_err("signing cert")?;
|
||||
|
||||
Ok((tonic::transport::Identity::from_pem(cert.pem(), keypair.serialize_pem()), cert))
|
||||
}
|
||||
75
src/main.rs
75
src/main.rs
|
|
@ -1,18 +1,29 @@
|
|||
use std::{
|
||||
env,
|
||||
io::Write,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
};
|
||||
|
||||
use eyre::{bail, ensure, Context, Result};
|
||||
|
||||
mod cert;
|
||||
mod server;
|
||||
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
use base64::Engine;
|
||||
use eyre::{bail, Context, Result};
|
||||
use tokio::net::UnixListener;
|
||||
use tonic::transport::{Certificate, ServerTlsConfig};
|
||||
use tracing::{info, Level};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
let addr = init_handshake();
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(Level::DEBUG)
|
||||
.with_writer(std::io::stderr)
|
||||
.without_time()
|
||||
.init();
|
||||
|
||||
let addr = match addr {
|
||||
let client_cert =
|
||||
std::env::var("PLUGIN_CLIENT_CERT").wrap_err("PLUGIN_CLIENT_CERT not found")?;
|
||||
let client_cert = Certificate::from_pem(client_cert);
|
||||
let (server_identity, server_cert) =
|
||||
cert::generate_cert().wrap_err("generating server certificate")?;
|
||||
|
||||
let (_tmpdir, socket) = match init_handshake(&server_cert).await {
|
||||
Ok(addr) => addr,
|
||||
Err(err) => {
|
||||
println!("{:?}", err);
|
||||
|
|
@ -20,40 +31,58 @@ async fn main() -> eyre::Result<()> {
|
|||
}
|
||||
};
|
||||
|
||||
let cert = std::env::var("PLUGIN_CLIENT_CERT").wrap_err("PLUGIN_CLIENT_CERT not found")?;
|
||||
let tls = ServerTlsConfig::new()
|
||||
.identity(server_identity)
|
||||
.client_auth_optional(true) // ??? terraform doesn't send certs ???
|
||||
.client_ca_root(client_cert);
|
||||
|
||||
|
||||
info!("Listening on {}", socket.display());
|
||||
|
||||
let uds = UnixListener::bind(socket).wrap_err("failed to bind unix listener")?;
|
||||
let uds_stream = tokio_stream::wrappers::UnixListenerStream::new(uds);
|
||||
|
||||
tonic::transport::Server::builder()
|
||||
.tls_config(tls)
|
||||
.wrap_err("invalid TLS config")?
|
||||
.add_service(server::tfplugin6::provider_server::ProviderServer::new(
|
||||
server::MyProvider,
|
||||
))
|
||||
.serve(addr)
|
||||
.serve_with_incoming(uds_stream)
|
||||
.await
|
||||
.wrap_err("failed to start server")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_handshake() -> Result<SocketAddr> {
|
||||
const _MAGIC_COOKIE_KEY: &str = "TF_PLUGIN_MAGIC_COOKIE";
|
||||
const _MAGIC_COOKIE_VALUE: &str =
|
||||
"d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d6991ca9872b2";
|
||||
|
||||
async fn init_handshake(server_cert: &rcgen::Certificate) -> Result<(tempfile::TempDir, PathBuf)> {
|
||||
// https://github.com/hashicorp/go-plugin/blob/8d2aaa458971cba97c3bfec1b0380322e024b514/docs/internals.md
|
||||
|
||||
let min_port = env::var("PLUGIN_MIN_PORT")
|
||||
let _ = env::var("PLUGIN_MIN_PORT")
|
||||
.wrap_err("PLUGIN_MIN_PORT not found")?
|
||||
.parse::<u16>()
|
||||
.wrap_err("PLUGIN_MIN_PORT not an int")?;
|
||||
let max_port = env::var("PLUGIN_MAX_PORT")
|
||||
let _ = env::var("PLUGIN_MAX_PORT")
|
||||
.wrap_err("PLUGIN_MAX_PORT not found")?
|
||||
.parse::<u16>()
|
||||
.wrap_err("PLUGIN_MAX_PORT not an int")?;
|
||||
|
||||
let port = min_port + 15; // chosen by a d20, lol
|
||||
ensure!(port < max_port);
|
||||
let tmpdir = tempfile::TempDir::new().wrap_err("failed to create temporary directory")?;
|
||||
let socket = tmpdir.path().join("plugin");
|
||||
|
||||
let addr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||
let addr = SocketAddr::new(addr, port);
|
||||
// https://github.com/hashicorp/go-plugin/blob/8d2aaa458971cba97c3bfec1b0380322e024b514/server.go#L426
|
||||
const CORE_PROTOCOL_VERSION: u8 = 1;
|
||||
const PROTO_VERSION: u8 = 6;
|
||||
let listener_addr_network = "unix";
|
||||
let listener_addr = socket.display();
|
||||
let proto_type = "grpc";
|
||||
let b64_cert = base64::prelude::BASE64_STANDARD_NO_PAD.encode(server_cert.der());
|
||||
|
||||
const VERSION: u8 = 6;
|
||||
println!("{CORE_PROTOCOL_VERSION}|{PROTO_VERSION}|{listener_addr_network}|{listener_addr}|{proto_type}|{b64_cert}");
|
||||
|
||||
println!("1|{VERSION}|tcp|{addr}|grpc");
|
||||
|
||||
Ok(addr)
|
||||
Ok((tmpdir, socket))
|
||||
}
|
||||
|
|
|
|||
193
src/server.rs
193
src/server.rs
|
|
@ -1,13 +1,32 @@
|
|||
#![allow(unused_variables, unused_imports)]
|
||||
|
||||
pub mod tfplugin6 {
|
||||
tonic::include_proto!("tfplugin6");
|
||||
}
|
||||
|
||||
use std::{collections::HashMap, vec};
|
||||
|
||||
use tfplugin6::provider_server::{Provider, ProviderServer};
|
||||
use tonic::{transport::Server, Request, Response, Result, Status};
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MyProvider;
|
||||
|
||||
fn empty_schema() -> tfplugin6::Schema {
|
||||
tfplugin6::Schema {
|
||||
version: 1,
|
||||
block: Some(tfplugin6::schema::Block {
|
||||
version: 0,
|
||||
attributes: vec![],
|
||||
block_types: vec![],
|
||||
description: "hello world".to_owned(),
|
||||
description_kind: 0,
|
||||
deprecated: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Provider for MyProvider {
|
||||
/// GetMetadata returns upfront information about server capabilities and
|
||||
|
|
@ -17,122 +36,166 @@ impl Provider for MyProvider {
|
|||
/// ignore the error and call the GetProviderSchema RPC as a fallback.
|
||||
async fn get_metadata(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::get_metadata::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::get_metadata::Response>, tonic::Status> {
|
||||
todo!()
|
||||
request: Request<tfplugin6::get_metadata::Request>,
|
||||
) -> Result<Response<tfplugin6::get_metadata::Response>, Status> {
|
||||
Err(Status::unimplemented(
|
||||
"GetMetadata: Not implemeneted".to_owned(),
|
||||
))
|
||||
}
|
||||
/// GetSchema returns schema information for the provider, data resources,
|
||||
/// and managed resources.
|
||||
async fn get_provider_schema(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::get_provider_schema::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::get_provider_schema::Response>, tonic::Status>
|
||||
{
|
||||
todo!()
|
||||
request: Request<tfplugin6::get_provider_schema::Request>,
|
||||
) -> Result<Response<tfplugin6::get_provider_schema::Response>, Status> {
|
||||
info!("Received get_provider_schema");
|
||||
let reply = tfplugin6::get_provider_schema::Response {
|
||||
provider: Some(empty_schema()),
|
||||
provider_meta: Some(empty_schema()),
|
||||
server_capabilities: Some(tfplugin6::ServerCapabilities {
|
||||
plan_destroy: true,
|
||||
get_provider_schema_optional: true,
|
||||
move_resource_state: false,
|
||||
}),
|
||||
data_source_schemas: HashMap::default(),
|
||||
resource_schemas: HashMap::from([("terustform_hello".to_owned(), empty_schema())]),
|
||||
functions: HashMap::default(),
|
||||
diagnostics: vec![],
|
||||
};
|
||||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
async fn validate_provider_config(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::validate_provider_config::Request>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<tfplugin6::validate_provider_config::Response>,
|
||||
tonic::Status,
|
||||
> {
|
||||
todo!()
|
||||
request: Request<tfplugin6::validate_provider_config::Request>,
|
||||
) -> Result<Response<tfplugin6::validate_provider_config::Response>, Status> {
|
||||
tracing::info!("validate_provider_config");
|
||||
|
||||
let reply = tfplugin6::validate_provider_config::Response {
|
||||
diagnostics: vec![],
|
||||
};
|
||||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
async fn validate_resource_config(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::validate_resource_config::Request>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<tfplugin6::validate_resource_config::Response>,
|
||||
tonic::Status,
|
||||
> {
|
||||
todo!()
|
||||
request: Request<tfplugin6::validate_resource_config::Request>,
|
||||
) -> Result<Response<tfplugin6::validate_resource_config::Response>, Status> {
|
||||
tracing::info!("validate_resource_config");
|
||||
|
||||
let reply = tfplugin6::validate_resource_config::Response {
|
||||
diagnostics: vec![],
|
||||
};
|
||||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
async fn validate_data_resource_config(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::validate_data_resource_config::Request>,
|
||||
) -> std::result::Result<
|
||||
tonic::Response<tfplugin6::validate_data_resource_config::Response>,
|
||||
tonic::Status,
|
||||
> {
|
||||
todo!()
|
||||
request: Request<tfplugin6::validate_data_resource_config::Request>,
|
||||
) -> Result<Response<tfplugin6::validate_data_resource_config::Response>, Status> {
|
||||
tracing::error!("validate_data_resource_config");
|
||||
todo!("validate_data_resource_config")
|
||||
}
|
||||
async fn upgrade_resource_state(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::upgrade_resource_state::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::upgrade_resource_state::Response>, tonic::Status>
|
||||
{
|
||||
todo!()
|
||||
request: Request<tfplugin6::upgrade_resource_state::Request>,
|
||||
) -> Result<Response<tfplugin6::upgrade_resource_state::Response>, Status> {
|
||||
tracing::error!("upgrade_resource_state");
|
||||
todo!("upgrade_resource_state")
|
||||
}
|
||||
/// ////// One-time initialization, called before other functions below
|
||||
async fn configure_provider(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::configure_provider::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::configure_provider::Response>, tonic::Status>
|
||||
{
|
||||
todo!()
|
||||
request: Request<tfplugin6::configure_provider::Request>,
|
||||
) -> Result<Response<tfplugin6::configure_provider::Response>, Status> {
|
||||
tracing::info!("configure_provider");
|
||||
let reply = tfplugin6::configure_provider::Response {
|
||||
diagnostics: vec![],
|
||||
};
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
/// ////// Managed Resource Lifecycle
|
||||
async fn read_resource(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::read_resource::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::read_resource::Response>, tonic::Status> {
|
||||
todo!()
|
||||
request: Request<tfplugin6::read_resource::Request>,
|
||||
) -> Result<Response<tfplugin6::read_resource::Response>, Status> {
|
||||
tracing::error!("read_resource");
|
||||
todo!("read_resource")
|
||||
}
|
||||
async fn plan_resource_change(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::plan_resource_change::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::plan_resource_change::Response>, tonic::Status>
|
||||
{
|
||||
todo!()
|
||||
request: Request<tfplugin6::plan_resource_change::Request>,
|
||||
) -> Result<Response<tfplugin6::plan_resource_change::Response>, Status> {
|
||||
tracing::error!("plan_resource_change");
|
||||
|
||||
let reply = tfplugin6::plan_resource_change::Response {
|
||||
planned_state: todo!(),
|
||||
requires_replace: vec![],
|
||||
planned_private: vec![],
|
||||
diagnostics: vec![],
|
||||
legacy_type_system: false,
|
||||
deferred: None,
|
||||
};
|
||||
|
||||
todo!("plan_resource_change")
|
||||
}
|
||||
async fn apply_resource_change(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::apply_resource_change::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::apply_resource_change::Response>, tonic::Status>
|
||||
{
|
||||
todo!()
|
||||
request: Request<tfplugin6::apply_resource_change::Request>,
|
||||
) -> Result<Response<tfplugin6::apply_resource_change::Response>, Status> {
|
||||
tracing::error!("apply_resource_change");
|
||||
|
||||
todo!("apply_resource_change")
|
||||
}
|
||||
async fn import_resource_state(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::import_resource_state::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::import_resource_state::Response>, tonic::Status>
|
||||
{
|
||||
todo!()
|
||||
request: Request<tfplugin6::import_resource_state::Request>,
|
||||
) -> Result<Response<tfplugin6::import_resource_state::Response>, Status> {
|
||||
tracing::error!("import_resource_state");
|
||||
|
||||
todo!("import_resource_state")
|
||||
}
|
||||
async fn move_resource_state(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::move_resource_state::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::move_resource_state::Response>, tonic::Status>
|
||||
{
|
||||
todo!()
|
||||
request: Request<tfplugin6::move_resource_state::Request>,
|
||||
) -> Result<Response<tfplugin6::move_resource_state::Response>, Status> {
|
||||
tracing::error!("move_resource_state");
|
||||
|
||||
todo!("move_resource_state")
|
||||
}
|
||||
async fn read_data_source(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::read_data_source::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::read_data_source::Response>, tonic::Status>
|
||||
{
|
||||
todo!()
|
||||
request: Request<tfplugin6::read_data_source::Request>,
|
||||
) -> Result<Response<tfplugin6::read_data_source::Response>, Status> {
|
||||
tracing::error!("read_data_source");
|
||||
|
||||
todo!("read_data_source")
|
||||
}
|
||||
/// GetFunctions returns the definitions of all functions.
|
||||
async fn get_functions(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::get_functions::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::get_functions::Response>, tonic::Status> {
|
||||
todo!()
|
||||
request: Request<tfplugin6::get_functions::Request>,
|
||||
) -> Result<Response<tfplugin6::get_functions::Response>, Status> {
|
||||
tracing::error!("get_functions");
|
||||
|
||||
todo!("get_functions")
|
||||
}
|
||||
/// ////// Provider-contributed Functions
|
||||
async fn call_function(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::call_function::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::call_function::Response>, tonic::Status> {
|
||||
todo!()
|
||||
request: Request<tfplugin6::call_function::Request>,
|
||||
) -> Result<Response<tfplugin6::call_function::Response>, Status> {
|
||||
tracing::error!("call_function");
|
||||
|
||||
todo!("call_function")
|
||||
}
|
||||
/// ////// Graceful Shutdown
|
||||
async fn stop_provider(
|
||||
&self,
|
||||
request: tonic::Request<tfplugin6::stop_provider::Request>,
|
||||
) -> std::result::Result<tonic::Response<tfplugin6::stop_provider::Response>, tonic::Status> {
|
||||
todo!()
|
||||
request: Request<tfplugin6::stop_provider::Request>,
|
||||
) -> Result<Response<tfplugin6::stop_provider::Response>, Status> {
|
||||
tracing::error!("stop_provider");
|
||||
|
||||
todo!("stop_provider")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue