Hashi @ Home

Platform identity services

consul nomad vault

Introduction and goals

Building a platform is all well and good, but it’s no use if only the Boss Man can use it. How do we set things up so that other people can actually access the services in the platform?

This is such an obvious question that it has gone overlooked in Hashi@Home until now, because I am the only one using the platform! Specific tokens issued to me by Vault, Nomad and Consul have been enough for me to access the services and be productive with the platform itself, but this is not acceptable in a scenario where there are (shock!) several users with different roles and permissions.

The context to remember is that we are doing this in order to allow customers to access our platform – access must be governed according to known policies, and be self-service.

This article is a small design study to with some practical considerations on how to allow our first users (platform operators) to access platform services.

We start with a discussion on the architecture and background, then show how the relevant tooling can be deployed in various scenarios to satisfy our initial goals1:

Operators should be able to access platform services with their own credentials

Requirements and Features

To be a bit more formal, instead of expressing a goal, we express a requirement with some features. The requirement becomes an “acceptance requirement” (we can accept the situation once some statements become true), which in turn express features that we can test.

Requirement: An Operator should be granted access to Platform Services based on their own credentials

Feature: Operators can use their home credentials to access a Platform Service

Scenario: Platform operator in platform owner role can access Vault with admin policy

  Given  I am user with owner role in assigned in their identity provider
  When   I log into the Vault web interface via the OIDC provider
  Then   The OIDC provider redirects me to my identity provider
  Then   I am asked to authenticate with my personal credentials
  And    The OIDC provider authorises my access to the service

The task then is to enable this feature for our platform.

After a brief digression into the definition of terms, we’ll design an implementation of the identity architecture that can be deployed in various scenarios to provide it. Although we’ll go a bit into the assumptions and standards (OAuth and OIDC – see later) we want to adopt, the full-blown details of how we exploit those for wider adoption for subsequent discussion.

Identity architecture

Let’s start by setting the stage for what we mean by some commonly-loaded terms.

When we say “identity”, we usually mean

a digital representation of a person which can be used to authenticate a human

Authentication is the means of providing a proof of your actual identity to a computer, but it is often accompanied by the natural next step: authorisation2. Authorisation is the means of granting permission to a human to access a given service, and when that access is granted, deciding on what level. Another term which is used to describe the “level” of permission is the “role” that the human identity assumes inside that service.

Put together, these two terms “authentication” and “authorisation” are often referred to as “AuthN/Z”, such is the frequency with which these concepts coexist. In the ancient past3, each service had an internal representation of its users, and therefore contained the identities as well as the policies for authorisation. One of the many downsides to that approach was that identities are duplicated across all of the services which the user wants to use4. In the enterprisey olden days5, a separation of concerns was introduced where an identity provider was introduced to the picture to act as a central source of truth for identities, but applications still contained their own authorisation engines internally. In this scenario, although the identities – and thus the user’s credentials (i.e. passwords) – were centrally managed and duplication of identities was addressed, each service still needed to be configured individually. The generalisation of this this approach gave us the Open ID standard, which provides a way to reliably exchange identities in a decentralised manner. The duplication in user identities was addressed, but there was still sprawl and lack of governance and compliance. Subsequently, the OAuth standard was developed, providing an authorisation layer to the identity infrastructure.

To make an long and complicated story considerably shorter6, the combination of these standards into identity providers and authorisation frameworks allows is to start speaking of “AAI”7:

“Authentication and Authorisation Infrastructure”: AAI

  • The set of standards and tools which permit the decentralisation of identities and access policies, permitting service providers and identity providers to independently manage their data.
  • The infrastructure which permits access to services based on policies defined by services

We are finally close to what we want for our platform - a thing that can define policies, as well as connect identities to services with permissions defined by those policies. We will be using Keycloak to implement this authorisation and authentication layer, finally giving us the ability to permit access to platform services without having to also manage the identities of our platform users.

We are going to call this the “Identity Architecture”.

Implementation of Identity Architecture

Now that we have a working definition identity architecture, let’s start working on an implementation as actual services with specific technologies.

The context of the platform identity service is shown in the diagram below, where it is designated “Authentication and Authorisation Service”.



  
  
  
    
      Platform Identity and Authorisation system
    
    
    
      
      Organisation
    
    
    
      
      Platform Services
    
    
    
      
      
      Platform
       
      user
    
    
    
      
      Identity
       
      Provider
    
    
    
      
      
      Platform
       
      Operator
    
    
    
      
      
      Platform
       
      Owner
    
    
    
      
      Platform
       
      Service
    
    
    
      
      Authentication
       
      and
      Authorisation
       
      Service
    
    
    
      
      
      Access
    
    
    
      
      
      Authorise
    
    
    
      
      
      
      Request
       
      Access
       
      Permissions
    
    
    
      
      
      
      Request
       
      Credentials
    
    
    
      
      
      Authenticate
    
    
    
      
      
      Provide
       
      Credentials
    
    
    
      
      
      Define
       
      access
       
      policies
       
      and
      permissions
    
    
    
      
      
      Deploy
       
      and
       
      Configure
    
    
      Legend
       
      
      person
      
      system
      
      external person
      
      external system
      
      
      
      
      
      
      
      
    
    
  

This service starts to provide the “Access” feature we described above in the introduction.

Feature: Access – Authentication and Authorisation workflows

Recall the scenario we initially described as an acceptance criterion? In that context, someone wishing to access one of the Platform Services8 is redirected to the AAI, which then requests authentication of the user at their organisation’s identity provider (IdP). After successful authentication, the AAI looks up what the user’s attributes as defined in the authorisation realm, and then passes those to the service which the user initially wanted to use.

The “platform service” we’re using in this acceptance requirement is the Platform Secrets engine, Hashicorp Vault. However, bear in mind that this requirement can and should be extended to all the other platform services (orchestrator/Nomad, service mesh/Consul, observability/Clickhouse, etc.)

The service’s policies then map those attributes to permissions and roles in its context, and authorises the user to access it with those same permissions and roles.

This is shown in the sequence diagram below:




  
    
      
    
  
  blockdiag
  seqdiag {
  User -> Service [label = "Request Service"];
  Service -> AAI [label = "Request AuthN/Z"];
  AAI -> IdP [label = "Authenticate User"];
  User -> IdP [label = "Provide Credentials"];
  IdP -> AAI [label = "Return Identity"];
  AAI -> Service [label = "Provide Attributes"];
  User <- Service [label = "Authorise with policy"];
}
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  User
  
  Service
  
  AAI
  
  IdP
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  Request Service
  Request AuthN/Z
  Authenticate User
  Provide Credentials
  Return Identity
  Provide Attributes
  Authorise with policy

As the platform owners, we need to deploy the AAI service, synonymous with “Platform Identity”. This means we are responsible for issuing standard-based attributes to authenticated identities, so that services can decide what permissions and roles to assign to those identities.

These abstract services take the following form in our platform:

Identity in the Platform Engineering Context

The Keycloak service will serve as the Identity service as part of the Platform Security Plane – let’s remind ourselves of the overall design as of summer 20269:

Keycloak sits in the ID management component of the Security Plane. The external identity store (The LDAP directory of identities) is not shown, because indeed it is not part of the platform, but actually owned by the organisation which is the customer of the platform. We attach our platform to it in order to allow the organisation to retain sovereignty over their identities.

Before we close the section on architecture, let’s take a closer look at the Keycloak container view where the platform operator wants to access the Vault instance:



  
    
    
    
    
    
      
        
        
      
      
        
          

Platform Operator

[person]

Platform

Internal developer platform. Contains several planes of services, designed to host customer-specified workloads.

Platform security plane

[Software System]

Contains services related to access, authoration, security for

  • platform services
  • platform personae
  • platform workloads

Platform Identity provider

[Container: OpenLDAP]

The backing service containing actual

human identity and source of truth

Authentication and Authorization

[Container: Keycloak]

Authentication and authorisation layer for humans and applications.

Secrets engine

[Container: Hashicorp Vault]

Platform service managing security and access for services. Human authentication via OIDC

Log In Authorize Authenticate Pass attributes authorize

Keycloak’s OIDC endpoint acts as a Vault Authentication method. Keycloak is configured to use the LDAP external identity store as a source of truth for identities. If the user us able to provide valid identification credentials for that identity, Keycloak returns valid authorisation claims, and Vault authorises access to the user.

So far, so good but this high-level view of the architecture does not yet reveal the dependencies in terms of platform services and flows.

The component view exposes what underlying platform components are needed:



  
    
    
    
    
    
      
        
      
      
        
          

Platform

Internal developer platform. Contains several planes of services, designed to host customer-specified workloads.

Platform security plane

[Software System]

Contains services related to access, authoration, security for

  • platform services
  • platform personae
  • platform workloads

Platform resources plane

[Software System]

Provides capacity for workloads in the platform

Platform Data plane

[Software System]

Platform services supporting data persistence for workloads

Platform workload orchestrator

[Container: Hashicorp Nomad]

The platform workload orchestration system

Platform Identity provider

[Container: OpenLDAP]

The backing service containing actual

human identity and source of truth

Authentication and Authorization

[Container: Keycloak]

Authentication and authorisation layer for humans and applications.

Secrets engine

[Container: Hashicorp Vault]

Platform service managing security and access for services. Human authentication via OIDC

Patroni Postgres manager

[Container: Patroni Postgres]

Patroni postgres manager

Platform compute

[Component: Docker, Podman]

CPU and memory resources available via the orchestrator

Platform storage

[Component]

Persistent storage and claims

Default postgres DB cluster

[Component: PostgreSQL]

Compute resources Persistent storage claim Compute resources Persistent storage claim State persistence DB External identity store

So, we have a fairly explicit C4 representation of the architecture. It shows what we need to bring to the table, and how we integrate with the platform itself. All we need to do is fill in the boxes with actual technology, and deploy the sucker!

Deployment Scenarios

Let’s define a few hypothetical deployment scenarios. These can be split grosso-modo between deployments in plane and out of plane.

“In plane” deployments place the services in the Platform Resource Plane, meaning that the services themselves are orchestrated by the platform itself while “out of plane” deployments mean that the services are not orchestrated by the platform, but by some external controller.

This external controller would be something independent, bound to the deploy environment and independently managed, such as a SystemD unit on a virtual machine, or a an independent orchestrator on an external control plane.

Combined deployment in-plane

In this scenario, we deploy the authentication and authorisation services as a single workload into the resource plane.

This means we write a single Nomad job which contains the relevant tasks and services for both the LDAP backing service, as well as the Keycloak service.



  
    
    
    
    
    
    





























    
      
        
        
        
      
      
        
          

PLATFORM SECURITY PLANE

NOMAD JOB

PLATFORM DATA PLANE

PATRONI POSTGRES MANAGER

VAULT NOMAD GROUP DEFAULT PSQL NETWORK TASKS SLAPD HTTP HEALTH OPENLDAP KEYCLOAK ADMIN CONFIGURATION CONFIGURATION EXPOSE RETRIEVE SECRETS EXPOSE RETRIEVE SECRETS EXPOSE EXPOSE LOOKUP

As can be seen in the scenario, we are dependent on the existence of a managed PostgreSQL cluster for the deployment of the Keycloak service, which will be expecting a database persistence layer for its data.

Discussion

In this scenario, we have the benefits of:

  1. Single definition of workload: we deploy everything we need with a single nomad.hcl file.
  2. Network namespace sharing: Keycloak can access the LDAP server on the local network since they share a Nomad network
  3. Allocation filesystem sharing: The Nomad allocation system makes available a shared filesystem for the deployment if they need to share files; each task also has access to its own private files and secrets.

However, we also have the potential downsides of:

  1. Resource exhaustion: Since both keycloak and ldap are declared in a single group, they will be placed on a single node. If there are not enough resources, the orchestrator may not be able to place them. This is particularly bad if the node hosting the allocation becomes unhealthy in the cluster.
  2. Monolithic deployment: Somewhat linked to this negative aspect is the fact that the deployment is monolithic - it becomes difficult to independently scale each of the tasks in it. This is not a concern to us initially, but is of great importance when we start to consider production readiness.

As a first step though, this is a good starting point. It is simple, easy to understand, self-contained, and has specific external dependencies.

Just for the sake of argument, let’s see what a factorised deployment would look like.

Factorised in-plane deployment

The factorised deployment differs from the combined deployment only in that there are two jobs instead of one:



  
    
    
    
    
    
    





























    
      
        
        
        
      
      
        
          

PLATFORM SECURITY PLANE

NOMAD JOB

NOMAD JOB

PLATFORM DATA PLANE

PATRONI POSTGRES MANAGER

VAULT NOMAD GROUP NOMAD GROUP DEFAULT PSQL NETWORK TASKS NETWORK TASKS SLAPD OPENLDAP HTTP HEALTH KEYCLOAK ADMIN CONFIGURATION CONFIGURATION EXPOSE RETRIEVE SECRETS EXPOSE RETRIEVE SECRETS EXPOSE EXPOSE LOOKUP LOOKUP

Discussion

In this scenario, we have almost artificially constructed the reverse of the pro/con set in the previous scenario, but we have introduced a coupling with the service mesh.

We can independently scale the services (Keycloak and LDAP), as well as make them independently resilient.

However, Keycloak will need to have fallback and backoff configuration in case the LDAP service is not discoverable either via DNS or Consul service catalogue lookup.

The failure case here would be that the the LDAP service becomes somehow unavailable, or the Keycloak server cannot connect to it, or perhaps if the template inside the task configuring the LDAP endpoint becomes stale for some reason, authentication would fail.

For now, we will eschew a full-blown risk analysis, contenting ourselves with the inspiration from these two scenarios.

Out-of-plane deployment

The out-of-plane deployment assumes that we have dedicated resources somewhere for these services. This may be conceived of as a more static deployment model, with dedicated machines in dedicated environments being allocated manually to this workload. The workloads (LDAP, Keycloak) and perhaps even their direct dependencies (Database backends, DNS resolvers, persistent storage claims, local filesystems, etc) are directly provisioned on these dedicated machines via some form of configuration management10

Final thoughts and summary

We stop short of making a decision of which scenario is “right” here, contenting ourselves with the fact that we have a few scenarios, and can discriminate on them based on what we need to achieve at the time.

A full production-ready deployment must take into account not just the deployment and operation of Day1, but also the ergonomics of change after that, as well as aiming for zero touch on operations.

That would require a deployment in a highly resilient and redundant environment, which we’ve seen mention and glimpses of, but not fully illustrated here.

Supporting those deployments could also be canary deployments where we factorise not just the application servers, but also their persistence layers, migrating them between deploys as we scale up blue, then down green deployments for zero-downtime upgrades.

There is still much analysis to perform, and experience to gain, but we can say at the end of this brief article that:

  1. We have a viable model for deploying platform identity based on the OAuth standard which we can deploy flexibly.
  2. It supports access to the platform services which our operators need to access using the OIDC Connect protocol
  3. If you already have a source of identities, such as an organisational directory, this can be modelled into the architecture and re-used.
  4. Similarly, organisational identities can be factored out from platform identities, but using the exact sample deployment model for the authorisation layer, just with different backing services.

Footnotes and References

  1. We do not yet go into the overall federation of services and access policies for federated users, but we will get there eventually! 

  2. Don’t come at me with your weird American spelling. I know everyone calls it AuthN/Z where that zed grinds my nerves, and I know I’m not going to be able to change it. But by Zeus I will spell my own writing properly! 

  3. I’m referring to the early to mid-2010s here. 

  4. That’s just one of the downsides – there are plenty others, ranging from security considerations, to operational and compliance concerns. 

  5. I’m referring to the early 2020’s here 

  6. The way I have described it here is far too simplified to be taken seriously, and mainly for my own purposes of creating a short narrative of what components we are deploying. The ecosystem comprising tools and standards to create AAI (Authentication and Authorisation Infrastructure) is far more complex, but it’s not my place to go into it here. 

  7. This is an incomplete definition of AAI, solely for the purposes of this article. Good luck finding an authoritative definition of AAI, and if you do, please send it to me. 

  8. Recall that when we say “Platform Service”, we are referring to services in platform planes – the orchestrator (Nomad), the secrets engine (Vault), etc. These are not the deployed workloads, often also called “services”, which are configured to use the federated AAI. In the case of EGI services, this AAI service would be Check-In. 

  9. As you can see, there are several parts missing. Some are missing by design (we don’t advocate a single IDE, we don’t have a portal, we are not interested in FinOps), while some are still waiting for proper integration (OPA as policy engine, SemGrep and Report Portal as code quality and testing). Stay tuned 

  10. I’m not coming right out to say it here, because I wanted to write this as if there were some arbitrary out-of-plane controller here, but it has become unwieldly talking around the fact that this is in all effects a set of Ansible playbooks against a set of VMs. 

Hashi@Home is personal side-project by brucellino. It's ok to watch, but don't touch. Get your own damn side project.