Skip to main content

innernet_server/
initialize.rs

1use crate::{
2    db::{self, DatabaseCidr, DatabasePeer},
3    ConfigFile, Interface, Path, ServerConfig,
4};
5use anyhow::{anyhow, Error};
6use clap::Parser;
7use colored::Colorize;
8use dialoguer::{theme::ColorfulTheme, Input};
9use indoc::printdoc;
10use innernet_publicip::Preference;
11use innernet_shared::{
12    prompts, CidrContents, Endpoint, IpNetExt, PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS,
13};
14use ipnet::IpNet;
15use rusqlite::{params, Connection};
16use std::net::{IpAddr, SocketAddr};
17use wireguard_control::KeyPair;
18
19fn create_database<P: AsRef<Path>>(
20    database_path: P,
21) -> Result<Connection, Box<dyn std::error::Error>> {
22    let conn = Connection::open(&database_path)?;
23    conn.pragma_update(None, "foreign_keys", 1)?;
24    // TODO(strohel): set stricter permissions on the db file?
25    conn.execute(db::peer::CREATE_TABLE_SQL, params![])?;
26    conn.execute(db::association::CREATE_TABLE_SQL, params![])?;
27    conn.execute(db::cidr::CREATE_TABLE_SQL, params![])?;
28    conn.pragma_update(None, "user_version", db::CURRENT_VERSION)?;
29    log::debug!("set database version to db::CURRENT_VERSION");
30
31    Ok(conn)
32}
33
34#[derive(Debug, Default, Clone, PartialEq, Eq, Parser)]
35pub struct InitializeOpts {
36    /// The network name (ex: evilcorp)
37    #[clap(long)]
38    pub network_name: Option<Interface>,
39
40    /// The network CIDR (ex: 10.42.0.0/16)
41    #[clap(long)]
42    pub network_cidr: Option<IpNet>,
43
44    /// This server's external endpoint (ex: 100.100.100.100:51820)
45    #[clap(long, conflicts_with = "auto_external_endpoint")]
46    pub external_endpoint: Option<Endpoint>,
47
48    /// Auto-resolve external endpoint
49    #[clap(long = "auto-external-endpoint")]
50    pub auto_external_endpoint: bool,
51
52    /// Port to listen on (for the WireGuard interface)
53    #[clap(long)]
54    pub listen_port: Option<u16>,
55}
56
57struct DbInitData {
58    network_name: String,
59    network_cidr: IpNet,
60    server_cidr: IpNet,
61    our_ip: IpAddr,
62    public_key_base64: String,
63    endpoint: Endpoint,
64}
65
66fn populate_database(conn: &Connection, db_init_data: DbInitData) -> Result<(), Error> {
67    const SERVER_NAME: &str = "innernet-server";
68
69    let root_cidr = DatabaseCidr::create(
70        conn,
71        CidrContents {
72            name: db_init_data.network_name.clone(),
73            cidr: db_init_data.network_cidr,
74            parent: None,
75        },
76    )
77    .map_err(|_| anyhow!("failed to create root CIDR"))?;
78
79    let server_cidr = DatabaseCidr::create(
80        conn,
81        CidrContents {
82            name: SERVER_NAME.into(),
83            cidr: db_init_data.server_cidr,
84            parent: Some(root_cidr.id),
85        },
86    )
87    .map_err(|_| anyhow!("failed to create innernet-server CIDR"))?;
88
89    let _me = DatabasePeer::create(
90        conn,
91        PeerContents {
92            name: SERVER_NAME.parse().map_err(|e: &str| anyhow!(e))?,
93            ip: db_init_data.our_ip,
94            cidr_id: server_cidr.id,
95            public_key: db_init_data.public_key_base64,
96            endpoint: Some(db_init_data.endpoint),
97            is_admin: true,
98            is_disabled: false,
99            is_redeemed: true,
100            persistent_keepalive_interval: Some(PERSISTENT_KEEPALIVE_INTERVAL_SECS),
101            invite_expires: None,
102            candidates: vec![],
103        },
104    )
105    .map_err(|_| anyhow!("failed to create innernet peer."))?;
106
107    Ok(())
108}
109
110pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Error> {
111    let theme = ColorfulTheme::default();
112
113    innernet_shared::ensure_dirs_exist(&[conf.config_dir(), conf.database_dir()]).map_err(
114        |_| {
115            anyhow!(
116                "Failed to create config and database directories {}",
117                "(are you not running as root?)".bold()
118            )
119        },
120    )?;
121    printdoc!(
122        "\nTime to setup your innernet network.
123
124        Your network name can be any hostname-valid string, i.e. \"evilcorp\", and
125        your network CIDR should be in the RFC1918 IPv4 (10/8, 172.16/12, or 192.168/16), 
126        or RFC4193 IPv6 (fd00::/8) ranges.
127
128        The external endpoint specified is a <host>:<port> string that is the address clients
129        will connect to. It's up to you to forward/open ports in your routers/firewalls
130        as needed.
131
132        For more usage instructions, see https://github.com/tonarino/innernet#usage
133        \n"
134    );
135
136    let name: Interface = if let Some(name) = opts.network_name {
137        name
138    } else {
139        Input::with_theme(&theme)
140            .with_prompt("Network name")
141            .interact()?
142    };
143
144    let root_cidr: IpNet = if let Some(cidr) = opts.network_cidr {
145        cidr
146    } else {
147        Input::with_theme(&theme)
148            .with_prompt("Network CIDR")
149            .with_initial_text("10.42.0.0/16")
150            // TODO(mbernat): workaround for https://github.com/console-rs/dialoguer/issues/330
151            .interact_text()?
152    };
153
154    let listen_port: u16 = if let Some(listen_port) = opts.listen_port {
155        listen_port
156    } else {
157        Input::with_theme(&theme)
158            .with_prompt("Listen port")
159            .default(51820)
160            .interact()
161            .map_err(|_| anyhow!("failed to get listen port."))?
162    };
163
164    log::info!("listen port: {}", listen_port);
165
166    let endpoint: Endpoint = if let Some(endpoint) = opts.external_endpoint {
167        endpoint
168    } else if opts.auto_external_endpoint {
169        let ip = innernet_publicip::get_any(Preference::Ipv4)
170            .ok_or_else(|| anyhow!("couldn't get external IP"))?;
171        SocketAddr::new(ip, listen_port).into()
172    } else {
173        let external_ip = prompts::ip_auto_detection_flow()?;
174        prompts::input_external_endpoint(external_ip, listen_port)?
175    };
176
177    let our_ip = root_cidr
178        .hosts()
179        .find(|ip| root_cidr.is_assignable(ip))
180        .unwrap();
181    let config_path = conf.config_path(&name);
182    let our_keypair = KeyPair::generate();
183
184    let config = ConfigFile {
185        private_key: our_keypair.private.to_base64(),
186        listen_port,
187        address: our_ip,
188        network_cidr_prefix: root_cidr.prefix_len(),
189    };
190    config.write_to_path(config_path)?;
191
192    let db_init_data = DbInitData {
193        network_name: name.to_string(),
194        network_cidr: root_cidr,
195        server_cidr: IpNet::new(our_ip, root_cidr.max_prefix_len())?,
196        our_ip,
197        public_key_base64: our_keypair.public.to_base64(),
198        endpoint,
199    };
200
201    // TODO(bschwind) - Clean up the config file and database
202    //                  if any errors occur in these init calls.
203
204    let database_path = conf.database_path(&name);
205    let conn = create_database(&database_path).map_err(|_| {
206        anyhow!(
207            "failed to create database {}",
208            "(are you not running as root?)".bold()
209        )
210    })?;
211    populate_database(&conn, db_init_data)?;
212
213    println!(
214        "{} Created database at {}\n",
215        "[*]".dimmed(),
216        database_path.to_string_lossy().bold()
217    );
218    printdoc!(
219        "
220        {star} Setup finished.
221
222            Network {interface} has been {created}, but it's not started yet!
223
224            Your new network starts with only one peer: this innernet server. Next,
225            you'll want to create additional CIDRs and peers using the commands:
226
227                {wg_manage_server} {add_cidr} {interface}, and
228                {wg_manage_server} {add_peer} {interface}
229            
230            See https://github.com/tonarino/innernet for more detailed instruction
231            on designing your network.
232        
233            When you're ready to start the network, you can auto-start the server:
234            
235                {systemctl_enable}{interface}
236
237    ",
238        star = "[*]".dimmed(),
239        interface = name.to_string().yellow(),
240        created = "created".green(),
241        wg_manage_server = "innernet-server".yellow(),
242        add_cidr = "add-cidr".yellow(),
243        add_peer = "add-peer".yellow(),
244        systemctl_enable = "systemctl enable --now innernet-server@".yellow(),
245    );
246
247    Ok(())
248}