@@ -26,7 +26,7 @@ use color_eyre::Result;
2626use futures:: { FutureExt , StreamExt } ;
2727use k8s_openapi:: api:: {
2828 apps:: v1:: Deployment ,
29- core:: v1:: { LoadBalancerIngress , LoadBalancerStatus , Service , ServiceStatus } ,
29+ core:: v1:: { LoadBalancerIngress , LoadBalancerStatus , Secret , Service , ServiceStatus } ,
3030} ;
3131use kube:: {
3232 api:: { Api , ListParams , Patch , PatchParams , ResourceExt } ,
@@ -47,7 +47,7 @@ use std::time::Duration;
4747use tracing:: { debug, error, info, instrument, trace, warn} ;
4848
4949use crate :: {
50- cloud:: Provisioner ,
50+ cloud:: { pwgen :: generate_password , Provisioner } ,
5151 ops:: {
5252 parse_provisioner_label_value, ExitNode , ExitNodeProvisioner , ExitNodeSpec , ExitNodeStatus ,
5353 EXIT_NODE_NAME_LABEL , EXIT_NODE_PROVISIONER_LABEL ,
@@ -218,7 +218,7 @@ async fn select_exit_node_local(
218218}
219219
220220#[ instrument( skip( ctx) ) ]
221- /// Returns the ExitNode resource for a Service resource, either finding an existing one or creating a new one
221+ /// Generates or returns an ExitNode resource for a Service resource, either finding an existing one or creating a new one
222222async fn exit_node_for_service (
223223 ctx : Arc < Context > ,
224224 service : & Service ,
@@ -258,7 +258,7 @@ async fn exit_node_for_service(
258258 return Ok ( exit_node) ;
259259 }
260260
261- let exit_node_tmpl = ExitNode {
261+ let mut exit_node_tmpl = ExitNode {
262262 metadata : ObjectMeta {
263263 name : Some ( exit_node_name. clone ( ) ) ,
264264 namespace : service. namespace ( ) ,
@@ -285,6 +285,11 @@ async fn exit_node_for_service(
285285 status : None ,
286286 } ;
287287
288+ let password = generate_password ( 32 ) ;
289+ let secret = exit_node_tmpl. generate_secret ( password. clone ( ) ) . await ?;
290+
291+ exit_node_tmpl. spec . auth = Some ( secret. metadata . name . unwrap ( ) ) ;
292+
288293 let serverside = PatchParams :: apply ( OPERATOR_MANAGER ) . validation_strict ( ) ;
289294
290295 let exit_node = nodes
@@ -332,6 +337,7 @@ async fn reconcile_svcs(obj: Arc<Service>, ctx: Arc<Context>) -> Result<Action,
332337 let obj = svc. clone ( ) ;
333338
334339 let node_list = nodes. list ( & ListParams :: default ( ) . timeout ( 30 ) ) . await ?;
340+
335341 // Find service binding of svc name/namespace?
336342 let existing_node = node_list. iter ( ) . find ( |node| {
337343 node. metadata
@@ -341,6 +347,7 @@ async fn reconcile_svcs(obj: Arc<Service>, ctx: Arc<Context>) -> Result<Action,
341347 . unwrap_or ( false )
342348 } ) ;
343349
350+ // XXX: Exit node manifest generation starts here
344351 let node = {
345352 if let Some ( node) = existing_node {
346353 node. clone ( )
@@ -523,13 +530,28 @@ async fn reconcile_nodes(obj: Arc<ExitNode>, ctx: Arc<Context>) -> Result<Action
523530
524531 return Ok ( Action :: await_change ( ) ) ;
525532 } else if is_managed {
526- // XXX: What the fuck.
527533 let provisioner = obj
528534 . metadata
529535 . annotations
530536 . as_ref ( )
531537 . and_then ( |annotations| annotations. get ( EXIT_NODE_PROVISIONER_LABEL ) )
532538 . unwrap ( ) ;
539+
540+ // We should assume that every managed exit node comes with an `auth` key, which is a reference to a Secret
541+ // that contains the password for the exit node.
542+ // If it doesn't exist, then it's probably bugged, and we should return and error
543+ let node_password = {
544+ let Some ( ref node_password_secret_name) = obj. clone ( ) . spec . auth else {
545+ return Err ( ReconcileError :: ManagedExitNodeNoPasswordSet ) ;
546+ } ;
547+ let secrets_api = Api :: namespaced ( ctx. client . clone ( ) , & obj. namespace ( ) . unwrap ( ) ) ;
548+ let secret: Secret = secrets_api. get ( node_password_secret_name) . await ?;
549+ let Some ( node_password) = secret. data . as_ref ( ) . unwrap ( ) . get ( "auth" ) else {
550+ return Err ( ReconcileError :: AuthFieldNotSet ) ;
551+ } ;
552+ String :: from_utf8_lossy ( & node_password. 0 ) . to_string ( )
553+ } ;
554+
533555 trace ! ( ?provisioner, "Provisioner" ) ;
534556 if let Some ( status) = & obj. status {
535557 // Check for mismatch between annotation's provisioner and status' provisioner
@@ -592,7 +614,8 @@ async fn reconcile_nodes(obj: Arc<ExitNode>, ctx: Arc<Context>) -> Result<Action
592614
593615 let provisioner_api = provisioner. clone ( ) . spec . get_inner ( ) ;
594616
595- let secret = provisioner
617+ // API key secret, do not use for node password
618+ let api_key_secret = provisioner
596619 . find_secret ( )
597620 . await
598621 . map_err ( |_| crate :: error:: ReconcileError :: CloudProvisionerSecretNotFound ) ?
@@ -603,24 +626,26 @@ async fn reconcile_nodes(obj: Arc<ExitNode>, ctx: Arc<Context>) -> Result<Action
603626 EXIT_NODE_FINALIZER ,
604627 obj. clone ( ) ,
605628 |event| async move {
606- let m: std:: prelude:: v1:: Result < Action , crate :: error:: ReconcileError > = match event
607- {
629+ let m: Result < _ , crate :: error:: ReconcileError > = match event {
608630 Event :: Apply ( node) => {
609- let _node = {
631+ let _ = {
632+ // XXX: We should get the value of the Secret and pass it in as node_password
610633 let cloud_resource = if let Some ( _status) = node. status . as_ref ( ) {
611634 info ! ( "Updating cloud resource for {}" , node. name_any( ) ) ;
612635 provisioner_api
613- . update_exit_node ( secret . clone ( ) , ( * node) . clone ( ) )
614- . await
636+ . update_exit_node ( api_key_secret . clone ( ) , ( * node) . clone ( ) , node_password )
637+ . await ?
615638 } else {
616639 info ! ( "Creating cloud resource for {}" , node. name_any( ) ) ;
617640 provisioner_api
618- . create_exit_node ( secret . clone ( ) , ( * node) . clone ( ) )
619- . await
641+ . create_exit_node ( api_key_secret . clone ( ) , ( * node) . clone ( ) , node_password )
642+ . await ?
620643 } ;
644+
645+ // unwrap should be safe here since in k8s it is infallible for a Secret to not have a name
621646 // TODO: Don't replace the entire status and object, sadly JSON is better here
622647 let exitnode_patch = serde_json:: json!( {
623- "status" : cloud_resource?
648+ "status" : cloud_resource,
624649 } ) ;
625650
626651 exit_nodes
@@ -641,7 +666,7 @@ async fn reconcile_nodes(obj: Arc<ExitNode>, ctx: Arc<Context>) -> Result<Action
641666 if is_managed {
642667 info ! ( "Deleting cloud resource for {}" , node. name_any( ) ) ;
643668 provisioner_api
644- . delete_exit_node ( secret , ( * node) . clone ( ) )
669+ . delete_exit_node ( api_key_secret , ( * node) . clone ( ) )
645670 . await
646671 . unwrap_or_else ( |e| {
647672 error ! ( ?e, "Error deleting exit node {}" , node. name_any( ) )
@@ -650,7 +675,6 @@ async fn reconcile_nodes(obj: Arc<ExitNode>, ctx: Arc<Context>) -> Result<Action
650675 Ok ( Action :: requeue ( Duration :: from_secs ( 3600 ) ) )
651676 }
652677 } ;
653-
654678 m
655679 } ,
656680 )
@@ -707,7 +731,7 @@ pub async fn run() -> color_eyre::Result<()> {
707731 client : client. clone ( ) ,
708732 } ) ,
709733 )
710- . for_each ( |_ | futures:: future:: ready ( ( ) ) )
734+ . for_each ( |result_value | futures:: future:: ready ( ( ) ) )
711735 . boxed ( ) ,
712736 ) ;
713737
@@ -732,7 +756,7 @@ pub async fn run() -> color_eyre::Result<()> {
732756 error_policy_exit_node,
733757 Arc :: new ( Context { client } ) ,
734758 )
735- . for_each ( |_ | futures:: future:: ready ( ( ) ) )
759+ . for_each ( |result_value | futures:: future:: ready ( ( ) ) )
736760 . boxed ( ) ,
737761 ) ;
738762
0 commit comments