1use bitwarden_access_connector::{
7 cli::{Cli, Command},
8 config::Config,
9 executor::RunExit,
10 instance_lock::{self, InstanceLockError},
11};
12use bitwarden_threading::cancellation_token::CancellationToken;
13use clap::Parser;
14use tracing_subscriber::{
15 EnvFilter, prelude::__tracing_subscriber_SubscriberExt as _, util::SubscriberInitExt as _,
16};
17
18#[tokio::main(flavor = "current_thread")]
19async fn main() {
20 let filter = EnvFilter::builder()
22 .with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
23 .from_env_lossy();
24
25 tracing_subscriber::registry()
26 .with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
27 .with(filter)
28 .init();
29
30 let cli = match Cli::try_parse() {
32 Ok(cli) => cli,
33 Err(e) => {
34 let _ = e.print();
36 std::process::exit(if e.use_stderr() { 1 } else { 0 });
37 }
38 };
39 let Command::Run(run_args) = cli.command;
40
41 let connector_cfg = match Config::from_cli(run_args) {
42 Ok(cfg) => cfg.into_access_connector_config(),
43 Err(e) => {
44 tracing::error!("startup error: {e}");
45 std::process::exit(1);
46 }
47 };
48
49 let _instance_lock = match instance_lock::acquire(connector_cfg.api_key_id()) {
52 Ok(lock) => Some(lock),
53 Err(InstanceLockError::AlreadyRunning) => {
54 tracing::error!(
55 "Another bwac process is already running with this access connector token. Stop \
56 it before starting this one."
57 );
58 std::process::exit(5);
59 }
60 Err(e) => {
61 tracing::warn!("{e}; continuing without the single-instance check");
62 None
63 }
64 };
65
66 let cancel = CancellationToken::new();
67
68 let watcher_cancel = cancel.clone();
69 tokio::spawn(async move {
70 wait_for_shutdown_signal().await;
71 tracing::info!("shutdown signal received; cancelling");
72 watcher_cancel.cancel();
73 });
74
75 let exit = bitwarden_access_connector::run(connector_cfg, cancel).await;
76
77 match exit {
78 RunExit::Shutdown => {
79 tracing::info!("access connector shut down cleanly");
80 std::process::exit(0);
81 }
82 RunExit::CredentialRefused => {
83 tracing::error!(
84 "The server rejected the access connector token. Check BWAC_TOKEN is current; \
85 if so, ask an admin for a new token and restart."
86 );
87 std::process::exit(2);
88 }
89 RunExit::StartupFailed => {
90 tracing::error!(
91 "Access connector could not authenticate at startup. Check network connectivity \
92 and the identity URL; restarting may succeed."
93 );
94 std::process::exit(4);
95 }
96 RunExit::NotEligible => {
97 tracing::error!(
98 "Access connector not eligible for rotation endpoints. Check: connector record not \
99 revoked or disabled, organisation license active, PAM included in the \
100 organisation plan."
101 );
102 std::process::exit(3);
103 }
104 }
105}
106
107async fn wait_for_shutdown_signal() {
109 #[cfg(unix)]
110 {
111 use tokio::signal::unix::{SignalKind, signal};
112
113 let mut sigterm = match signal(SignalKind::terminate()) {
114 Ok(s) => s,
115 Err(e) => {
116 tracing::warn!("failed to install SIGTERM handler: {e}");
117 tokio::signal::ctrl_c()
119 .await
120 .unwrap_or_else(|e| tracing::warn!("ctrl_c error: {e}"));
121 return;
122 }
123 };
124
125 tokio::select! {
126 _ = sigterm.recv() => {
127 tracing::info!("received SIGTERM");
128 }
129 result = tokio::signal::ctrl_c() => {
130 if let Err(e) = result {
131 tracing::warn!("ctrl_c error: {e}");
132 } else {
133 tracing::info!("received Ctrl-C");
134 }
135 }
136 }
137 }
138
139 #[cfg(not(unix))]
140 {
141 if let Err(e) = tokio::signal::ctrl_c().await {
142 tracing::warn!("ctrl_c error: {e}");
143 } else {
144 tracing::info!("received Ctrl-C");
145 }
146 }
147}