HTTPS Proxy
Introduction
In addition to securing your SSH accesses, by splitting the authentication part (ingress connection) and the authorization part (egress connection), The Bastion can do a similar job for HTTPS connections.
Note that there is an overhead (depending on your hardware setup) of several hundreds of milliseconds for each query-response trip, due to the fact that multiple processes are spawned for each query, to ensure proper security containment to the calling account's system user. It's probably a bad idea to use on a multi-million queries/day workload, or if each added millisecond to the query-response trip impacts the QoS of your service.
The primary use is for network devices, that happen to have more and more HTTPS APIs in addition
to the usual conf terminal available through SSH. As the same commands are usually available from
HTTPS and SSH on these devices, it would be too bad to secure the access to SSH through the bastion,
but leave direct access to their HTTPS API!
Query workflow
The workflow is similar to the one used by SSH, e.g. two distinct connections (ingress and egress), with the egress connection using credentials stored on the bastion:
A client makes an HTTP request to the proxy, with the following information embedded in:
The type of request (GET or POST)
The complete URI, including the host of the remote HTTPS server it would like to send the request to
Potential body data for POST requests
Credentials to authenticate to the proxy on the ingress connection, namely the bastion account name and its proxy password (set by
selfGenerateProxyPassword)User name to use to authenticate on the remote HTTPS server (for the egress connection)
The bastion checks the provided credentials to authenticate the request against a known account (authentication part)
The bastion verifies whether the just-authenticated account has access rights to connect to the remote server as the specified remote user (authorization part)
The bastion uses the (group or personal) credentials stored on the bastion, to passthrough the HTTP request to the remote server, as the specified remote user
The bastion forwards the response to the client
Setting up the HTTPS Proxy
You should enable the HTTPS Proxy daemon, and configure it. Please check the osh-http-proxy.conf for more information.
Running a query through the proxy
First try
Once the proxy is running, we can try to query it:
curl https://bastion1.example.org:8443/
No authentication provided, and authentication is mandatory
Of course, the proxy only accepts to work when one is properly authenticated to it. To do this, one should have an account on the bastion, and use the selfGenerateProxyPassword command so that a new ingress password is set for their account. They'll then be able to authenticate to the proxy using the HTTP basic-auth method, and try to send a request to a remote server. To keep a high compatibility with HTTP clients and libraries that can be used on the ingress side, all the additional data required by the bastion to properly authenticate, authorize and passthrough the request is encoded in the user part of the widely supported HTTP Authorize header (basic-auth). The password part corresponds to the password we've generated just above.
The format of the user part is as follows:
BASTION_ACCOUNT@REMOTE_USER@REMOTE_HOST%REMOTE_PORT
The %REMOTE_PORT part is optional, and defaults to 443 if omitted. For example, to send a GET /info request to the remote network device named router12.example.org on the default port 443, using the remote account monitoring, through the bastion1.example.org bastion, having the HTTPS Proxy listening on its port 8443 and a bastion account robot-mon, one can use curl:
curl -u robot-mon@monitoring@router12.example.org https://bastion1.example.org:8443/info
Enter host password for user 'robot-mon@monitoring@router12.example.org':
This account doesn't have access to this user@host tuple (Access denied for robot-mon to monitoring@router12.example.org:443)
A password will be prompted: the password generated by selfGenerateProxyPassword should be entered.
Remember: this is to authenticate yourself to the bastion (ingress connection), then the bastion will authenticate
itself to the remote machine (egress connection), using credentials stored on the bastion,
that your account must have access to.
In the above case, we entered the password correctly, but our account doesn't have access to the requested host monitoring@router12.example.org. This is what we need to do now.
Query headers
The following optional headers can be added by the client to its request, to modify the behavior of the proxy. They're only interpreted by the bastion, and are not forwarded to the remote device.
- X-Bastion-Timeout
Timeout, in seconds, for the connection between the bastion and the remote device. Must be an integer, and defaults to the
timeoutoption of the osh-http-proxy.conf.- X-Bastion-Egress-Protocol
Protocol to use for the connection between the bastion and the remote device, either
httpsorhttp. Defaults tohttps. The protocol must be allowed by theallowed_egress_protocolsoption of the osh-http-proxy.conf.- X-Bastion-Allow-Downgrade
Set to
1to allow the bastion to silently retry the request using TLSv1.0, if the remote device doesn't support any more recent TLS version. Defaults to0.- X-Bastion-Enforce-Secure
Set to
1to enforce the verification of the remote device's certificate, even if theinsecureoption of the osh-http-proxy.conf is enabled. Defaults to0.
Only a few standard headers of the client request are forwarded to the remote device, namely Accept,
Content-Type, Content-Length and Content-Encoding.
Headers sent to the remote device
The bastion adds the following headers to the request it sends to the remote device, which can be used for traceability purposes on the device side:
- X-Bastion-Auth-Mode
How the egress credentials have been chosen, see the
X-Bastion-Auth-Moderesponse header below.- X-Bastion-Ingress-Client-IP
IP of the client that sent the request to the bastion.
- X-Bastion-Ingress-Client-Port
Source port of the client that sent the request to the bastion.
- X-Bastion-Ingress-Client-User-Agent
User-Agent of the client that sent the request to the bastion.
- X-Bastion-Ingress-Account
The bastion account of the client that sent the request.
- X-Bastion-UniqID
The unique ID of the request on the bastion, to correlate with the bastion logs.
- X-Bastion-Instance
The hostname of the bastion instance that handled the request.
Response headers
The bastion adds the following headers to its response to the client.
Note that the headers sent by the remote device are not forwarded as-is to the client, except Content-Type
and Content-Length. Some of the below headers are only present when relevant.
- Server
The Bastion version that handled the request.
- X-Bastion-Instance
The hostname of the bastion instance that handled the request.
- X-Bastion-ReqID
The unique ID of the request on the bastion, to correlate with the bastion logs.
- X-Bastion-Local-Status
Status of the request from the bastion standpoint. When the request has been forwarded to the remote device, this is
200 OK, and the HTTP status of the response is the one of the remote device (seeX-Bastion-Remote-Statusbelow). Otherwise, this is the error encountered by the bastion, for example403 Access Deniedor504 Device Timeout, and the HTTP status of the response matches it.- X-Bastion-Remote-Host
The remote device host, as specified by the client.
- X-Bastion-Remote-IP
The IP of the remote device, as resolved by the bastion.
- X-Bastion-Auth-Mode
How the egress credentials have been chosen:
self/explicitorgroup/explicitwhen the client specified which credentials to use,self/defaultorgroup/guessedwhen the bastion chose them from the accesses of the account, orlegacy.- X-Bastion-Request-Length
Length, in bytes, of the body of the client request.
- X-Bastion-Remote-Status
Only present when the request has been forwarded to the remote device (i.e. when
X-Bastion-Local-Statusis200 OK): this is the HTTP status returned by the remote device, and the HTTP status of the response matches it. If the bastion couldn't get an answer from the remote device, for example because the connection or the TLS negotiation failed, this is a500error describing the issue, such as500 Can't connect to 192.0.2.1:443.- X-Bastion-Remote-Server
The
Serverheader returned by the remote device, if any.- X-Bastion-Remote-Location
When the remote device answered with a redirect that the bastion didn't follow, this is the absolute URL of the redirect target, as computed from the
Locationheader returned by the device. TheLocationheader itself is not forwarded, as it usually points directly to the remote device and not to the bastion. To follow this redirect, the client can send a new request to the bastion for this target, which will go through the usual access checks. Which redirects are followed by the bastion depends on theallow_redirectsoption of the osh-http-proxy.conf.- X-Bastion-Remote-Client-SSL-Version
TLS version used for the connection to the remote device, for example
TLSv1_3, when usinghttpsas the egress protocol.- X-Bastion-Remote-Client-SSL-Cipher
TLS cipher used for the connection to the remote device, when using
httpsas the egress protocol.- X-Bastion-Remote-Client-SSL-Cert-Subject
Subject of the certificate presented by the remote device, when using
httpsas the egress protocol.- X-Bastion-Remote-Client-SSL-Cert-Issuer
Issuer of the certificate presented by the remote device, when using
httpsas the egress protocol.- X-Bastion-Remote-Client-SSL-Warning
Warning about the TLS connection to the remote device, if any, for example
Peer certificate not verifiedwhen theinsecureoption is enabled.- X-Bastion-Remote-Client-SSL-Cert-Verify
Only present when the
insecureoption of the osh-http-proxy.conf is enabled (andX-Bastion-Enforce-Secureis not set), when usinghttpsas the egress protocol. Even if the certificate of the remote device is not enforced in this case, the bastion still verifies it, exactly as it would have ifinsecurewas disabled, and reports the result here:OKif the connection would have been accepted, orFAILEDfollowed by the reason between parentheses, for exampleFAILED (self-signed certificate)orFAILED (certificate doesn't match myhost.example). This is a diagnostic information, to ease the migration of an infrastructure toinsecure: false, by detecting beforehand which devices would then be refused, for example by monitoring this header, or by looking for it in the HTTP Proxy request logs. The same information is also logged to syslog, see TLS compliance logs below.
All the X-Bastion-Remote-Client-SSL-* headers are generated by the bastion itself:
if the remote device sends such headers (including through HTML <meta http-equiv> tags), they're ignored.
If the bastion followed redirects, they describe the TLS connection that returned the final answer.
- X-Bastion-Egress-Timing
Time spent, in microseconds, on the request between the bastion and the remote device.
- X-Bastion-Downgraded
Present and set to
1when the bastion had to downgrade the connection to TLSv1.0, seeX-Bastion-Allow-Downgradeabove.
TLS compliance logs
The bastion logs to syslog, with the info level, one line of type http-proxy-tls per TLS connection made
to a remote device, regardless of the insecure option of the osh-http-proxy.conf.
It tells whether this connection has been accepted or refused because of the remote device's certificate.
When insecure is enabled, the certificate is not enforced, so the line tells instead whether the connection
would have been accepted if insecure was disabled (the same verification as for the
X-Bastion-Remote-Client-SSL-Cert-Verify header described above). These lines also include the TLS version
and cipher used, which can be used for compliance statistics.
Note that there can be more than one such line per request, if the bastion had to downgrade the connection,
or followed a redirect. Connections that failed for another reason than the certificate validity
(e.g. connection refused, or no common TLS version or cipher) are not logged here.
Each line contains the following fields, in addition to the usual ones (such as uniqid, which can be used
to find the corresponding request in the access logs):
account,auth_mode,user: the account that did the request, how the egress credentials were chosen, and the remote userhost,ip,port: the remote device, as specified by the client, its resolved IP, and its port. For a redirect followed by the bastion, these are the ones of the redirect target, so that each line describes the device whose certificate has been verified (withallow_redirectsset toany, this can be another device than the one the client asked for)enforced:1if the certificate validity was enforced (insecuredisabled, orX-Bastion-Enforce-Secureset by the client),0otherwiseverdict:okif the connection has been accepted (or, whenenforcedis0, would have been accepted ifinsecurewas disabled),failedotherwisereason: why it has been (or would have been) refused, empty ifverdictisokssl_version,ssl_cipher: the TLS version and cipher used for the connection, empty if it has been refusedcert_subject,cert_issuer: the subject and issuer of the certificate presented by the remote device, empty if the connection has been refuseddowngraded:1if the bastion had to downgrade the connection to TLSv1.0,0otherwise
For example, to list the remote devices that are (or would be, once insecure is disabled) refused:
grep 'http-proxy-tls' /var/log/bastion/bastion.log | grep 'verdict="failed"' | grep -Eo 'host="[^"]*" ip="[^"]*" port="[^"]*"' | sort | uniq -c
Access declaration
The access check is the same than the one done for SSH accesses, which means that oneself can have access to a remote host either through a personal access or a group access.
To get granted access to a remote device, through a personal access, either the selfAddPersonalAccess or the accountAddPersonalAccess shall be used (both are restricted commands) such as:
bssh --osh accountAddPersonalAccess --host router12.example.org --port 443 --user monitoring --force
Note the use of --force to skip the SSH connection test, which is useless in our case.
To use a group access instead, one of the aclkeepers of the group should use groupAddServer, such as:
bssh --osh groupAddServer --group netdevices --host router12.example.org --port 443 --user monitoring --force
Egress password
For personal accesses
If access to a remote device is granted to you through a personal access (using either the selfAddPersonalAccess
or accountAddPersonalAccess commands), you must first generate a new set of credentials that will be stored
on your bastion account, for egress connections. This is the equivalent of your personal egress keys for SSH,
but in that case it's a password that will be used to authenticate using basic-auth to the remote server.
You can generate this password using the selfGeneratePassword command:
bssh --osh selfGeneratePassword --do-it
*------------------------------------------------------------------------------*
|THIS IS A PRIVATE COMPUTER SYSTEM, UNAUTHORIZED ACCESS IS STRICTLY PROHIBITED.|
|ALL CONNECTIONS ARE LOGGED. IF YOU ARE NOT AUTHORIZED, DISCONNECT NOW. |
*------------------------------------------------------------------------------*
╭──bastion1.example.org───────────────────────────────the-bastion-3.03.99-rc1───
│ ▶ generating a new egress password for your account
├───────────────────────────────────────────────────────────────────────────────
│ Generated a new password of length 16 for your account, robot-mon, hashes follow:
│ md5crypt: $1$G0fo$2DH2OJQJ9bMgo5fUUuPeK.
│ sha256crypt: $5$2xd1aGuD$ze7px3olXdjWthSrdnzelm6avzT2kszx/voXms8/V00
│ sha512crypt: $6$udw2UNLs$tQ1p7ZYraOT4Woh1ZCGJNf.UAIh09nXPBf4ejpRurOY/fJUs6Dgh1WdkpY4pdCvKMQrPeetB42bNTSzIwJyGi1
│ This new password will now be used by default.
╰─────────────────────────────────────────────────────</selfGeneratePassword>───
As you can see, the password is stored on your bastion account, and is not printed: only its hashes are. With this information, the corresponding remote account can be provisioned on the device (usually, a network device). In our above example, an account named monitoring would have to be created on the remote device, using one of these hashes. Prefer to use the most secure hashing algorithm supported by the remote device.
To get your password (hash) list, you can use selfListPasswords:
bssh --osh selfListPasswords
*------------------------------------------------------------------------------*
|THIS IS A PRIVATE COMPUTER SYSTEM, UNAUTHORIZED ACCESS IS STRICTLY PROHIBITED.|
|ALL CONNECTIONS ARE LOGGED. IF YOU ARE NOT AUTHORIZED, DISCONNECT NOW. |
*------------------------------------------------------------------------------*
╭──bastion1.example.org───────────────────────────────the-bastion-3.03.99-rc1───
│ ▶ list your egress passwords
├───────────────────────────────────────────────────────────────────────────────
│ Current password created at Tue Jun 22 15:42:10 2021 by robot-mon
│ ... md5crypt: $1$G0fo$2DH2OJQJ9bMgo5fUUuPeK.
│ ... sha256crypt: $5$2xd1aGuD$ze7px3olXdjWthSrdnzelm6avzT2kszx/voXms8/V00
│ ... sha512crypt: $6$udw2UNLs$tQ1p7ZYraOT4Woh1ZCGJNf.UAIh09nXPBf4ejpRurOY/fJUs6Dgh1WdkpY4pdCvKMQrPeetB42bNTSzIwJyGi1
│
│ Fallback password 1 created at Wed Jun 2 08:00:01 2021 by robot-mon
│ ... md5crypt: $1$qF0M$2.rbRRGs66aPiEpc/SqGv/
│ ... sha256crypt: $5$E9qkC7D6$SG8BB.nXvwU0dB0Bq9S/sF5pDidLwSIDKCv95qNWhX0
│ ... sha512crypt: $6$druGNgSk$bzVHSvux/OOE2ZhDpabFekQU3GTsiKS7Yl/lLmb9gIAmjnFfR6gj7GzOniK2jdLtEcB/hQlhcx9TDgj5zHhVd.
│
╰────────────────────────────────────────────────────────</selfListPasswords>───
If the selfGeneratePassword command is used several times, the newly generated password will always override
the previous one. Still, all the previous passwords are kept (archived) for good measure, and can be restored
manually by a bastion admin. These passwords are named Fallback passwords in the output of selfListPasswords.
For group accesses
If the access to the remote device is given through a group, then the group's own credentials will be used. To this effect, one of the group owners should use the groupGeneratePassword command:
bssh --osh groupGeneratePassword --group netdevices --do-it
╭──bastion1.example.org───────────────────────────────the-bastion-3.03.99-rc1───
│ ▶ generating a new egress password for the group
├───────────────────────────────────────────────────────────────────────────────
│ Generated a new password of length 16 for group netdevices, hashes follow:
│ md5crypt: $1$9sb2$X8/pPBSLfQ0ddBGR/bzsT1
│ sha256crypt: $5$o6Jr8w0X$yQfLuX17tUwE1jfhhAX//vsn6KpXU5jUd7SCNbkYNH.
│ sha512crypt: $6$gyxMyjao$YNhZJPXZa4r838XKg2tfvvoV/Dtm5HKsyKt18BnvFfT.y.hZuSXRX9GhM4mA0hUsO9f0UBisO/WiK3vF/9qsL1
│ This new password will now be used by default.
╰────────────────────────────────────────────────────</groupGeneratePassword>───
As with the personal egress passwords, the password is stored on the bastion only, and is not printed: only its hashes are. With this information, the corresponding remote account can be provisioned on the device (usually, a network device). In our above example, an account named monitoring would have to be created on the remote device, using one of these hashes. Prefer to use the most secure hashing algorithm supported by the remote device.
To get the group's password (hash) list, one can use the groupListPasswords command:
bssh --osh groupListPasswords --group netdevices
*------------------------------------------------------------------------------*
|THIS IS A PRIVATE COMPUTER SYSTEM, UNAUTHORIZED ACCESS IS STRICTLY PROHIBITED.|
|ALL CONNECTIONS ARE LOGGED. IF YOU ARE NOT AUTHORIZED, DISCONNECT NOW. |
*------------------------------------------------------------------------------*
╭──bastion1.example.org───────────────────────────────the-bastion-3.03.99-rc1───
│ ▶ list the egress passwords of the group
├───────────────────────────────────────────────────────────────────────────────
│ Current password created at Tue Jun 29 10:21:38 2021 by slesimpl
│ ... md5crypt: $1$9sb2$X8/pPBSLfQ0ddBGR/bzsT1
│ ... sha256crypt: $5$o6Jr8w0X$yQfLuX17tUwE1jfhhAX//vsn6KpXU5jUd7SCNbkYNH.
│ ... sha512crypt: $6$gyxMyjao$YNhZJPXZa4r838XKg2tfvvoV/Dtm5HKsyKt18BnvFfT.y.hZuSXRX9GhM4mA0hUsO9f0UBisO/WiK3vF/9qsL1
╰───────────────────────────────────────────────────────</groupListPasswords>───
If the groupGeneratePassword command is used several times, the newly generated password will always
override the previous one. Still, all the previous passwords are kept (archived) for good measure,
and can be restored manually by a bastion admin.
These passwords are named Fallback passwords in the output of groupListPasswords.