feature: users have a first name, a last name and an e-mail address.
Release / release (push) Successful in 11s
Release / release (push) Successful in 11s
This commit is contained in:
1 parent
b9003bacb9
commit
08693f7e68
9 files changed
+872
-290
No files matched your search
@@ -27,7 +27,7 @@ port is reachable from the external interface only when it is in `exposed_ports`
|
||||
in `DOCKER-USER` are not touched, because the package owns only its own chain. Restarting Docker
|
||||
restores the rules. The rules cover IPv4 only.
|
||||
|
||||
## ADR-2: A status web service of the standard library, bound to the internal address
|
||||
## ADR-2: A small status web service, bound to the internal address
|
||||
|
||||
Status: Accepted
|
||||
|
||||
@@ -35,15 +35,15 @@ Context: The router has no way to see its state without a shell. A check from th
|
||||
network, by a person or by a monitor, needs a page or an endpoint. The router is an
|
||||
internet-facing host, so what the service shows and where it listens matter.
|
||||
|
||||
Decision: The `vpn-router-web` unit runs one Python script that uses only the standard library, in
|
||||
the way of a small test server. It listens on the address of the internal interface and on
|
||||
Decision: The `vpn-router-web` unit runs one Python script on the standard library's
|
||||
`http.server`, with the package's own modules and the Python packages of the Ubuntu archive that
|
||||
the package depends on, and no web framework. It listens on the address of the internal interface and on
|
||||
`web.port`, 7081 by default, and is on unless `web.enabled` is `false` (ADR-7). `/` is for
|
||||
people, in plain text for `curl` and `wget` and as a Tailwind CSS page for a browser, and
|
||||
`/status` is the same status as JSON. It shows the state of the services, the package version
|
||||
and the state of the site-to-site, road-warrior, OpenVPN and WireGuard features, and nothing from
|
||||
the configuration. `vpn-router-setup` opens the port on the internal interface in UFW and writes
|
||||
the features and the facts it resolves to `/run/vpn-router/web.json`, so the service does not
|
||||
need the package's configuration code, which needs Jinja2.
|
||||
the features and the facts it resolves to `/run/vpn-router/web.json`.
|
||||
|
||||
Consequences: Nothing outside the internal network can reach it, since it listens on the internal
|
||||
address only and the firewall opens the port on the internal interface only. `/` and `/status`
|
||||
@@ -57,14 +57,14 @@ browser on a network without internet shows the unstyled page, and `curl` is una
|
||||
Status: Accepted
|
||||
|
||||
Context: A client such as the Router Manager needs the configuration and the resolved state of
|
||||
the router, which `/` and `/status` must not show. The service has only the standard library,
|
||||
listens on plain HTTP on the internal network, and its configuration file is readable by root
|
||||
only.
|
||||
the router, which `/` and `/status` must not show. The service listens on plain HTTP on the
|
||||
internal network, and its configuration file is readable by root only. The platform that deploys
|
||||
the router generates one secret and gives it to the router's configuration and to the client.
|
||||
|
||||
Decision: `/config` needs a JWT in an `Authorization: Bearer` header. The service verifies HS256
|
||||
tokens with `hmac` and `hashlib`, with the secret `web.jwt_secret`, and accepts no other
|
||||
algorithm, because verifying RS256 or ES256 needs a library outside the standard library. The
|
||||
token must carry `exp`, not more than ten minutes ahead, so that the client makes a fresh token for
|
||||
algorithm, because a shared secret is what both sides have; RS256 or ES256 would need a key pair
|
||||
and its distribution. The token must carry `exp`, not more than ten minutes ahead, so that the client makes a fresh token for
|
||||
each use and a leaked one expires. Without a secret the page is not served. The configuration
|
||||
shown has no secret options (ADR-8).
|
||||
|
||||
@@ -125,24 +125,27 @@ checks certificates only; removing a user revokes the certificate for both. A us
|
||||
certificate expires after the CA's default of a year and is renewed by removing and adding the
|
||||
user again.
|
||||
|
||||
## ADR-6: The web service manages users by running vpn-router-user
|
||||
## ADR-6: The web service and vpn-router-user share one module for users
|
||||
|
||||
Status: Accepted
|
||||
|
||||
Context: The web service has only the standard library (ADR-2), and managing users needs
|
||||
`python3-ldap` and `simple-ca`. Its unit is sandboxed with `ProtectSystem=strict`.
|
||||
Context: Users are managed from the command line, by an operator at a terminal, and through
|
||||
`/users` of the web service, by the Router Manager. Both must apply the same rules to names,
|
||||
passwords and person attributes, and both change the directory and the CA. `simple-ca` keeps
|
||||
the CA's state in one file, so two changes must not run at the same time. The web unit is
|
||||
sandboxed with `ProtectSystem=strict`.
|
||||
|
||||
Decision: `/users` runs `vpn-router-user` and returns its JSON answer, mapping its exit status
|
||||
to the HTTP status. Passwords go to the program on standard input, so they never appear in a
|
||||
command line. The unit allows writing the paths the program needs: `/run/vpn-router` for its
|
||||
lock, `/etc/vpn-router/pki` for the certificates and the CA's state, and `/etc/swanctl/x509crl`
|
||||
for the published CRL. A lock serializes the program's runs, because `simple-ca` keeps its state
|
||||
in one file.
|
||||
Decision: `vpnrouter_users.py` holds the user operations, the checks and a session that takes a
|
||||
lock and the connection of the user manager account. The web service imports it and maps a
|
||||
`Failure` to the HTTP status; `vpn-router-user` imports it and adds the argument parsing and
|
||||
the output, text for people and JSON with `--json`. The web unit allows writing the paths the
|
||||
operations need: `/run/vpn-router` for the lock, `/etc/vpn-router/pki` for the certificates and
|
||||
the CA's state, and `/etc/swanctl/x509crl` for the published CRL.
|
||||
|
||||
Consequences: The web service imports nothing outside the standard library, and the same program
|
||||
serves the command line and the API. Each request starts a process, which is fine for user
|
||||
administration. The API is protected by the token of `/config`, so whoever has the secret can
|
||||
manage users.
|
||||
Consequences: The rules exist once. A request starts no process, and a password never leaves
|
||||
the process that received it. The web service imports `python3-ldap` and the package's
|
||||
configuration module. The API is protected by the token of `/config`, so whoever has the secret
|
||||
can manage users.
|
||||
|
||||
## ADR-7: The web service starts on its own and reads its inputs at each request
|
||||
|
||||
@@ -154,13 +157,13 @@ whatever state setup is in, also before setup has run and while it fails. The in
|
||||
|
||||
Decision: The service depends on nothing to start and does not end on its own. A discovery
|
||||
thread works out, every 30 seconds, the internal address from `vpn-router.json` as
|
||||
`general.mode` directs, with the discovery code in `vpnrouter_net.py`, which uses only the
|
||||
standard library and which `vpnrouter.py` uses too, and `web.port` and `web.enabled` from the
|
||||
same file. It runs the HTTP server in a thread of its own at that address and port, stops it and
|
||||
starts a new one when they change, and runs none while the service is disabled, the address is
|
||||
not known or it cannot be bound. Each request reads the token secret from `vpn-router.json` and
|
||||
the features and resolved facts from `web.json`, which setup writes, because they need the
|
||||
configuration code; while that file is missing the router shows no features. The unit is not
|
||||
`general.mode` directs, with the discovery code in `vpnrouter_net.py`, which `vpnrouter.py` uses
|
||||
too, and `web.port` and `web.enabled` from the same file. It runs the HTTP server in a thread of
|
||||
its own at that address and port, stops it and starts a new one when they change, and runs none
|
||||
while the service is disabled, the address is not known or it cannot be bound. Each request
|
||||
reads the token secret from `vpn-router.json` and the features and resolved facts from
|
||||
`web.json`, which setup writes when it applies the configuration, so the service shows what setup
|
||||
applied; while that file is missing the router shows no features. The unit is not
|
||||
ordered after `vpn-router-setup`, and setup does not restart it.
|
||||
|
||||
Consequences: The status page answers as soon as the internal address exists, whatever state
|
||||
@@ -223,3 +226,24 @@ profile needs OpenVPN to be enabled.
|
||||
Consequences: slapd always holds the directory and the CA always exists on a configured router,
|
||||
even when no P2S system uses them. An operator who wants a CA of their own must place it before
|
||||
choosing the platform.
|
||||
|
||||
## ADR-11: Groups are groupOfNames entries that never become empty
|
||||
|
||||
Status: Accepted
|
||||
|
||||
Context: Users are to be organised in groups. Groups record membership only; nothing on the
|
||||
router reads them. LDAP clients that read groups, such as `openvpn-auth-ldap` and the LDAP
|
||||
connectors of OIDC providers, expect `groupOfNames` with member DNs, and OpenLDAP's core schema
|
||||
requires a `groupOfNames` to have at least one `member`.
|
||||
|
||||
Decision: A group is `cn=<name>,ou=groups,dc=vpn-router,dc=local`, a `groupOfNames` with the
|
||||
DNs of its members and an optional `description`. A group is created with at least one member,
|
||||
and taking out its last member is refused; such a group is removed instead. Removing a user
|
||||
takes it out of its groups and is refused while the user is the last member of one. The group
|
||||
operations are in `vpnrouter_users.py` with the user operations, so they share the session and
|
||||
its lock; `vpn-router-group` is a tool of its own beside `vpn-router-user`, and the web service
|
||||
serves them under `/groups`. Group names follow the rules of user names.
|
||||
|
||||
Consequences: A group always satisfies the schema without a placeholder member, and an empty
|
||||
group cannot exist. Removing a user or a member can need another step first. A later use of
|
||||
groups, such as allowing OpenVPN only to some, can read them as they are.
|
||||
Reference in new issue
Block a user