Choria is traditionally a loosely coupled system with very few central components. In certain environments centralised AAA / RBAC is desired, this package delivers that.
Read the introductory blog post for background and motivation
The main motivation is to avoid the problems caused by having to do certificate management for every user, instead you have a central login and central authority who does AAA for every request. This is more appropriate to the typical Enterprise environment, typical users will be happy with the default behavior.
With this deployed the workflow becomes:
$ mco ping
The ping application failed to run, use -v for full error backtrace details: could not find token in environment variable CHORIA_TOKEN
$ mco login
Username (rip):
Password:
Starting a new shell with CHORIA_TOKEN set, please exit when done
$ mco ping
---- ping statistics ----
19 replies max: 161.60 min: 131.23 avg: 151.21
The token is valid for a configurable period after which time another mco login
will be required. Users are able to perform only the actions that they are entitled. Users have no SSL certificates of their own - a system wide certificate might be needed to connect to middleware if configured to require TLS.
This is under active development, see the Issues list for current outstanding items. Documentation and deployment details are sparse while it's being worked on, though its functional and we are happy to help you on our slack channel.
- Authentication
- Okta identity cloud
- Static configured users with support for basic agent+action ACLs as well as Open Policy Agent policies
- Capable of running centrally separate from signers
- Supports setting the Choria Organization claim for multi tenancy (not for okta users)
- Authorization
- JWT token claims based allow list for access to agents and actions
- JWT token claims based Open Policy Agent rego files
- Auditing
- Log file based auditing
- Messages published to NATS Stream
- Messages published to NATS JetStream
- Signing
- JWT token based signer
- Does not require access to the login service
- Stateless capable of running regionally in clusters behind load balancers with no shared storage needs
- Convenient
mco login
tool for authenticating to the service - Prometheus stats
- CLI for encrypting secrets using bcrypt
- Only supports HTTPS with verification disabled as clients lack certificates in this model
Review the below Features Reference section and pick the authentication, signer and auditing systems that suits your needs.
You can choose to run the login service and the signer service in different locations - you might have a central login service with access to Okta but regional DCs only signing requests and never needing access to Okta.
Additionally you need:
- A privileged certificate signed by the CA your Choria network trusts, by default these should be have CNAMEs
something.privileged.mcollective
- A key and certificate used for signing the JWT tokens, does not need to be from any particular CA
- A key, certificate and CA file used to serve HTTPS requests, signed by a CA in the same chain as the clients
- Configuration for the signer and the Choria framework.
There is a Puppet module exaldraen-choria_aaasvc which will automate the installation and configuration of the AAA service, but currently still requires you to provision the certificates manually.
Packages for Enterprise Linux can be found on our Packagecloud and we have a Docker container that can be used to host the service in Kubernetes at choria/aaasvc.
The signer uses a JSON file for configuration and lets you compose the system as you need it, below is a basic example:
{
"choria_config": "/etc/choria/signer/choria.conf",
"logfile": "/var/log/aaasvc.log",
"loglevel": "warn",
"authenticator": "userlist",
"auditors": ["logfile"],
"authorizer": "actionlist",
"signer": "basicjwt",
"port": 8080,
"monitor_port": 8081,
"site": "london",
"tls_certificate": "/etc/choria/signer/tls/cert.pem",
"tls_key": "/etc/choria/signer/tls/key.pem",
"tls_ca": "/etc/choria/signer/tls/ca.pem",
"basicjwt_signer": {
"signing_certificate": "/etc/choria/signer/signer/pub.pem",
"max_validity":"2h"
},
"logfile_auditor": {
"logfile": "/var/log/choria_signer/audit.log"
},
"userlist_authenticator": {
"signing_key": "/etc/choria/signer/signing/key.pem",
"validity": "1h",
"users": [
{
"username": "puppetadmin",
"password": "$2y$05$c4b/0WZ5WJ3nhSZPN9m8keCUPlCYtNOTkqU4fDNEPCUy1C9Pfqn2e",
"acls": [
"puppet.*"
],
"properties": {
"group": "admins"
},
"organization": "acme"
},
{
"username": "admin",
"password": ".....",
"properties": {
"group": "admins"
},
"opa_policy_file": "/etc/choria/signer/common.rego",
"organization": "acme"
}
]
}
}
Key | Description |
---|---|
choria_config | Configuration for the Choria framework, example below |
logfile | Logfile for the service running info |
loglevel | Level to log at - debug, info, warn |
authenticator | The name of the authenticator to use, see feature reference |
auditors | List of auditors to use, see feature reference |
authorizer | Authorizer to use, see feature reference |
signer | Signer to use, see feature reference |
port | HTTP port to listen on for request |
monitor_port | Port to serve prometheus stats on |
site | Site to expose in prometheus stats |
tls_certificate | Certificate used on the listening port |
tls_key | Key used on the listening port |
tls_ca | CA used to validate clients |
basicjwt_signer | Configuration for the Basic JWT signer |
logfile_auditor | Configuration for the Logfile Auditor |
natsstream_auditor | Configuration for the NATS Stream auditor |
okta_authenticator | Configuration for the Okta authenticator |
userlist_authenticator | Configuration for the User List authenticator |
The Choria configuration file just need to configure the security system with the location of the privileged certificate:
plugin.security.provider = file
plugin.security.file.certificate = /etc/choria/signer/choria/signer.privileged.mcollective_cert.pem
plugin.security.file.key = /etc/choria/signer/choria/signer.privileged.mcollective_key.pem
plugin.security.file.ca = /etc/choria/signer/choria/privileged_ca.pem
There are many certificates here, lets look at the ones listed in the samples above:
File | Description |
---|---|
signer/choria/*.pem | Certificate, key and CA used to sign requests within your DC, issued by the CA in the DC |
signer/signing/*.pem | Certificate and Key used to sign JWT tokens, no particular need for a specific CA to sign it |
signer/tls/*.pem | Certificate, Key and CA Chain that will communicate with clients requesting tokens and logins, clients will disable verify when connecting |
Once configured you can use curl to test your login works:
curl -s --request POST -d '{"username":"puppetadmin", "password":"secret"}' -H "Content-type: application/json" -k https://localhost:8080/choria/v1/login
You should get a token back that you can decode using jwt.io, it will look as below:
{
"agents": [
"rpcutil.ping",
"*",
"puppet.*"
],
"callerid": "[email protected]",
"exp": 1547742762
}
You can configure your system wide Choria CLI with lines like here, this will enable it for everyone:
# usual settings omitted
plugin.choria.security.request_signer.url = https://localhost:8080/choria/v1/sign
plugin.choria.security.request_signer.token_environment = CHORIA_TOKEN
You'll need to set the token in your shell:
export CHORIA_TOKEN=$(curl -s --request POST -d '{"username":"puppetadmin", "password":"secret"}' -H "Content-type: application/json" https://localhost:8080/choria/v1/login | jq -r .token)
At this point you can use mco
cli as always, requests will be sent to the signer for signing, users will not need their own certificates or use mco choria request_cert
Authentication is the act of validating a person is who he claims to be, this is done using a username, token, 2FA or other similar means.
This supports a number of authentication schemes each would issue a JWT token to the user that will then be authorized and signed by a Signer.
While authentication is provided by this tool, it's optional you might choose to create JWT tokens using another method of your choosing, the login feature will only be enabled if any authenticator is configured.
If using a service for this isn't for you you can also configure users and groups statically.
Passwords are encrypted using bcrypt
, you can use apache httpasswd to encrypt the passwords:
% echo secret |caaa crypt
$2y$05$c4b/0WZ5WJ3nhSZPN9m8keCUPlCYtNOTkqU4fDNEPCUy1C9Pfqn2e
{
"authenticator": "userlist",
"userlist_authenticator": {
"signing_key": "/etc/choria/signer/signing_key.pem",
"validity": "1h",
"users": [
{
"username": "puppetadmin",
"password": "$2y$05$c4b/0WZ5WJ3nhSZPN9m8keCUPlCYtNOTkqU4fDNEPCUy1C9Pfqn2e",
"acls": [
"puppet.*"
],
"organization": "acme"
},
{
"username": "admin",
"password": ".....",
"opa_policy_file": "/etc/choria/signer/common.rego",
"organization": "acme"
}
]
}
}
You can also keep the users list outside of this file in which case it will be reread when mtime
changes.
{
"authenticator": "userlist",
"userlist_authenticator": {
"signing_key": "/etc/choria/signer/signing_key.pem",
"validity": "1h",
"users_file": "/etc/choria/signer/users.json"
}
}
Where users.json
would have:
[
{
"username": "puppetadmin",
"password": "$2y$05$c4b/0WZ5WJ3nhSZPN9m8keCUPlCYtNOTkqU4fDNEPCUy1C9Pfqn2e",
"acls": [
"puppet.*"
],
"organization": "acme"
},
{
"username": "admin",
"password": ".....",
"opa_policy_file": "/etc/choria/signer/common.rego",
"organization": "acme"
}
]
Okta is an identity cloud providing users, authentication and group membership as a service. They have a great free tier suitable for many small sites and so is a good first step towards moving your users to a managed service.
This tool can authenticate users against Okta and retrieve the groups they belong to, based on those groups access is granted to certain Choria agents and actions.
Once you signed up for Okta and set up a application for Choria you'll get endpoints, client id, client secret and api token, put this in the configuration here.
{
"authenticator": "okta",
"okta_authenticator": {
"client_id": "xxx",
"client_secret": "xxx",
"api_token": "xxx",
"endpoint": "https://xxx.oktapreview.com",
"validity": "1h",
"signing_key": "/etc/choria/signer/signing_key.pem",
"acls": {
"Everyone": ["rpcutil.ping"],
"ChoriaAdmins": ["*"],
"ChoriaPuppetAdmins": ["puppet.*"]
}
}
}
Here we configure acls
based on Okta groups - all users can rpcutil ping
, there are Puppet admins with appropriate rights and fleet wide admins capable of managing anything.
Authorization is how you declare what an authenticated user can do, in this system the JWT tokens can contain either a simple agent/action list or a full features Open Policy Agent based policy.
Authorization is how you declare what an authenticated user can do, in this system the JWT tokens have an agents
claim with the following:
The authorizers will then inspect this and determine if the user should be allowed to make a request he is requesting we sign.
This authorizer reads the agents
claim in the JWT token and allow/deny the user. Examples below.
{
"agents": [
"rpcutil.ping",
"puppet.*"
]
}
*
- all actions are allowedpuppet.status
- one specific action is allowedpuppet.*
- all actions in the puppet agent are allowed
Multiple agent entries can be listed in the claim and any that match will allow the request otherwise it gets denied.
{
"authorizer": "actionlist"
}
It has no specific configuration.
The Open Policy Agent based policies allow for very flexible policy to be embedded into the JWT tokens, it allow for policies we have never supported in the past:
- Ensuring filters are used to avoid huge blast radius requests by accident
- Ensuring specific fact, class or identity filters are used
- Ensuring a specific collective is used
- Contents of the JWT claim
- Checks based on the site the aaasvc is deployed in
- Checks on every input being sent to the action
Here's a complex policy:
# must be in this package
package io.choria.aaasvc
# it only checks `allow`, its good to default false
default allow = false
# user can deploy only frontend of myco into production but only in malta
allow {
input.action == "deploy"
input.agent == "myco"
input.data.component == "frontend"
requires_fact_filter("country=mt")
input.collective == "production"
}
# can ask status anywhere in any environment
allow {
input.action == "status"
input.agent == "myco"
}
# user can do anything myco related in development
allow {
input.agent == "myco"
input.collective == "development"
}
Here we use the requires_fact_filter()
to ensure a specific fact filter is used, we have these custom functions:
requires_filter()
- ensures that at least one of identity, class, compound of fact filters is not emptyrequires_fact_filter("country=mt")
- ensures the specific fact filter is present in the requestrequires_class_filter("apache")
- ensures the specific class filter is present in the requestrequires_identity_filter("some.node")
- ensures the specific identity filter is present in the request
And you'll have these input items at your disposal:
agent
- the agent being invokedaction
- the action being invokeddata
- the contents of the request - all the inputs being sent to the actionsender
- the sender hostcollective
- the targeted sub collectivettl
- the ttl of the requesttime
- the time the request was madesite
- the site hosting the aaasvcs (from its config)claims
- all the JWT claims
You can store this in a file and specify the user in the userlist plugin like this:
{
"username": "admin",
"password": ".....",
"opa_policy_file": "/etc/choria/signer/admin.rego"
}
To activate this authorizer configure it like this:
{
"authorizer": "opa"
}
The Signing service signs requests on behalf of CLI users, ths signing service has certificates that the Choria network trusts (known as privileged certificates). The signer validates the JWT token is valid before signing.
Signers do not communicate directly with the Authentication service so you can run a central authenticator and signers in every location.
The only supported signer today is one that receives JWT tokens as issued by Okta or Userlist authentications, it inspects the request and should the token allow the request it will sign it and audit it.
{
"signer": "basicjwt",
"basicjwt_signer": {
"signing_certificate": "/etc/choria/signer/signing_cert.pem",
"max_validity": "24h"
}
}
The signing_certificate
here is the public part of the signing key used by the Authenticators. This is used to validate that the JWT was indeed issued by a trusted Authenticator.
max_validity
is the maximum amount of time from the present that the received JWT token is allowed in its exp
field, this avoid infinite length tokens from being issued that can be a huge security risk.
Tokens without exp
will be denied in all cases.
Every signing action gets audited via an auditor, multiple auditors can be active at the same time and will be called in series.
This is a simple auditor that just writes a logfile of the actions taken.
{
"auditors": ["logfile"],
"logfile_auditor": {
"logfile": "/var/log/signer_audit.json"
}
}
You have to arrange for rotation of this log file, each line will be a JSON line.
If you want to aggregate audit logs from your regional signers back to the central authentication service this is the auditor to use.
It publish structured messages to a NATS Stream topic that you can use the Choria Stream Replicator to transport these from your regional DC to central for consumption.
Published messages will match the io.choria.signer.v1.signature_audit JSON Schema.
{
"auditors": ["natsstream"],
"natsstream_auditor": {
"cluster_id": "test-cluster",
"servers": "nats://localhost:4222",
"topic": "audit"
}
}
The JetStream auditor is similar to the NATS Stream one but publishes to the upcoming JetStream Streaming Server.
Published messages will match the io.choria.signer.v1.signature_audit JSON Schema.
{
"auditors": ["jetstream"],
"jetstream_auditor": {
"servers": "nats://localhost:4222",
"topic": "audit"
}
}
When monitor_port
is set Prometheus statistics are reported on /metrics
Statistic | Description |
---|---|
choria_aaa_authenticator_errors | Total number of authentication requests that failed |
choria_aaa_authenticator_time | Total time taken to perform authentication |
choria_aaa_authorizer_allowed | Total number of requests that were allowed by the authorizer |
choria_aaa_authorizer_denied | Total number of requests that were denied authorizer |
choria_aaa_authorizer_errors | Total number of requests that could not be authorized |
choria_aaa_auditor_errors | Total number of audit requests that failed |
choria_aaa_auditor_natsstream_reconnects | Total number of times the NATS Streaming auditor reconnected to the middleware |
choria_aaa_signer_errors | Total number of requests that could not be signed |
choria_aaa_signer_allowed | Total number of requests that were allowed by the signer |
choria_aaa_signer_denied | Total number of requests that were denied by the signer |
choria_aaa_signer_invalid_token | Total number of signing requests that had invalid JWT tokens |