Post

Building a Secure Engineering, Secrets & PKI Platform with OpenBao, Microsoft Entra ID, and Proxmox

How I designed a private security-engineering platform that combines Proxmox, Microsoft Entra ID, OpenBao, PKI, project-scoped RBAC, and certificate-based Microsoft Graph authentication.

Building a Secure Engineering, Secrets & PKI Platform with OpenBao, Microsoft Entra ID, and Proxmox

Building a Secure Engineering, Secrets & PKI Platform with OpenBao, Microsoft Entra ID, and Proxmox

I spend a significant part of my work around Microsoft 365 security, Microsoft Entra ID, Microsoft Graph, PowerShell automation, and security assessments.

As the number of internal security-engineering projects I was working on and supervising increased, one infrastructure problem became increasingly obvious:

How do I give different security projects the credentials and certificates they need without distributing privileged secrets across developer machines, configuration files, repositories, and scripts?

The answer eventually became a small but fairly complete private security-engineering platform built around:

  • Proxmox VE;
  • Ubuntu Linux;
  • Docker;
  • internal DNS and TLS;
  • Microsoft Entra ID;
  • OpenBao;
  • X.509 PKI;
  • project-scoped authorization;
  • Microsoft Graph app-only authentication.

The goal was not simply to deploy OpenBao.

The goal was to build a security boundary around the development environment itself.


Project at a Glance

AreaImplementation
ObjectiveCentralize secrets and certificate issuance for internal Microsoft Security engineering projects
InfrastructureProxmox VE, Ubuntu Server, Docker Compose
Human authenticationMicrosoft Entra ID via OIDC
AuthorizationOpenBao Identity + project-scoped templated policies
SecretsOpenBao KV v2
PKIDedicated Root CA + operational Intermediate CA
Workload authenticationClient secret and X.509 certificate
Microsoft integrationEntra App Registrations + Microsoft Graph
Private-key modelPrefer locally generated, non-exportable keys + CSR signing
AuditingOpenBao file audit logging
StorageOpenBao Integrated Storage / Raft
Recovery modelShamir unseal + Raft snapshots
Security principlesLeast privilege, separation of duties, project isolation, credential lifecycle management

The initial OpenBao integration was intentionally scoped to the projects that actually required centralized Microsoft Graph credentials and PKI rather than forcing every project to depend on OpenBao.


The Problem

A security tool querying Microsoft Graph often needs an Entra application identity.

That identity may require:

1
2
3
4
5
Tenant ID
Application / Client ID
Client Secret
or
Certificate + Private Key

The simplest development pattern is also one of the worst:

1
2
3
4
5
6
Developer workstation
├── .env
├── config.json
├── certificate.pfx
├── password.txt
└── PowerShell variables

It works.

It also creates several problems.

Credential sprawl

Secrets start existing in multiple places:

  • local files;
  • PowerShell scripts;
  • shell history;
  • repositories;
  • exported PFX files;
  • chat messages;
  • temporary configuration files.

Weak isolation

If multiple projects share credentials, compromising one project can expose another.

Poor attribution

When several tools authenticate with the same application identity, it becomes much harder to determine which project performed a particular operation.

Certificate lifecycle problems

Self-signed certificates generated independently on developer machines quickly become difficult to manage.

Who issued them?

When do they expire?

Where is the private key?

Can the private key be exported?

Was the certificate ever replaced?

Excessive administrative access

A common shortcut in labs is to let everyone use the administrative credential because implementing proper authorization takes longer.

That was explicitly something I wanted to avoid.

The target model became:

1
2
3
4
5
6
7
8
9
Human identity
      ↓
Authenticated centrally
      ↓
Receives only project-specific access
      ↓
Uses only its project's workload credentials
      ↓
Workload receives only required Microsoft Graph permissions

Design Goals

Before configuring OpenBao, I defined several design principles.

1. Human identity must be separate from workload identity

An engineer authenticating to the secrets platform is not the same identity as the application authenticating to Microsoft Graph.

1
2
3
4
5
6
7
Human
  ↓
Microsoft Entra ID
  ↓
OIDC
  ↓
OpenBao

is separate from:

1
2
3
4
5
6
7
Security Tool
  ↓
Entra App Registration
  ↓
Certificate / Client Secret
  ↓
Microsoft Graph

This separation sounds obvious, but collapsing those two identity planes is an easy mistake to make in small environments.


2. Projects must not share application identities by default

Each project should have its own:

1
2
3
4
5
6
Entra App Registration
OpenBao secret path
PKI authorization role
Certificate
Private key
Microsoft Graph permissions

This provides a much cleaner security boundary.

It also improves:

  • auditing;
  • revocation;
  • credential rotation;
  • troubleshooting;
  • permission reviews;
  • accountability.

3. Authentication must not imply authorization

Microsoft Entra ID answers:

Who is this person?

OpenBao answers:

What is this person allowed to retrieve or issue?

Those are intentionally separate decisions.


4. Protecting the credential does not reduce its Graph privilege

This became one of the most important architectural principles.

OpenBao controls:

Who can access the credential?

Microsoft Entra ID controls:

What can the application do with that credential?

The complete authorization model therefore looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Engineer
   │
   ▼
Microsoft Entra ID
   │
   │ OIDC
   ▼
OpenBao
   │
   │ OpenBao Policy
   ▼
Project Credential
   │
   ▼
Entra App Registration
   │
   │ Application Permissions
   ▼
Microsoft Graph

A perfectly protected certificate attached to an excessively privileged application is still an excessively privileged application.

Secret management and cloud authorization are separate security problems.


High-Level Architecture

The platform sits inside my private lab infrastructure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
                         Microsoft Entra ID
                               │
                               │ OIDC
                               ▼
                       ┌─────────────────┐
                       │     OpenBao     │
                       ├─────────────────┤
                       │ Identity        │
                       │ Policies        │
                       │ Audit           │
                       ├─────────────────┤
                       │ KV v2           │
                       │ PKI Root        │
                       │ PKI Intermediate│
                       └────────┬────────┘
                                │
              ┌─────────────────┴─────────────────┐
              │                                   │
              ▼                                   ▼
       Project A Credential                Project B Credential
              │                                   │
              ▼                                   ▼
       Entra App Registration              Entra App Registration
              │                                   │
              └──────────────┬────────────────────┘
                             │
                             ▼
                     Microsoft Graph

Underneath that security layer:

1
2
3
4
5
6
7
8
9
10
11
Proxmox VE
    │
    ▼
Ubuntu Server
    │
    ├── Docker / Docker Compose
    ├── OpenBao
    ├── Internal DNS
    ├── Reverse proxy services
    ├── Private source control
    └── Secure remote connectivity

The infrastructure is intentionally small, but the security boundaries are designed to resemble patterns used in larger environments.


Infrastructure Layer

The underlying platform runs on Proxmox VE.

A dedicated Ubuntu Server virtual machine hosts containerized infrastructure services through Docker Compose.

The wider environment includes:

ComponentPurpose
Proxmox VEVirtualization
Ubuntu ServerContainer host
Docker ComposeService deployment
Pi-holeInternal DNS
NginxReverse proxying
ForgejoPrivate source control
TailscalePrivate remote connectivity
OpenBaoSecrets, identity-aware authorization and PKI

An internal home.arpa DNS namespace is used for lab services, while internally issued certificates provide TLS trust between managed systems.

The intention was to avoid using raw IP addresses throughout the environment and instead operate services through predictable DNS identities.


Deploying OpenBao

OpenBao became the central security component of the platform.

The implementation uses:

1
2
3
4
5
6
7
OpenBao 2.6.x
Integrated Storage / Raft
TLS
Shamir unseal
File audit logging
OpenBao UI
Microsoft Entra ID OIDC

OpenBao’s data is persisted through its integrated Raft storage backend.

Conceptually:

1
2
3
4
storage "raft" {
  path    = "/openbao/data"
  node_id = "openbao-01"
}

The real deployment is containerized with persistent storage mounted from the Ubuntu host.

I selected Raft because it keeps the OpenBao data layer self-contained rather than adding another external database dependency to a relatively small environment.


Seal and Recovery Model

The initial deployment uses Shamir key splitting.

1
2
Key shares: 5
Threshold:  3

This means three valid shares are required to unseal the platform.

The important part was not simply enabling Shamir.

It was understanding what the recovery material represents.

The following should not be stored together casually:

1
2
3
4
5
6
7
8
9
OpenBao data
+
unseal shares
+
initial root token
+
TLS private keys
+
backup material

Doing so would largely defeat the security boundary.

The initial root token is therefore treated as bootstrap/emergency material rather than a routine login mechanism.

Day-to-day human access happens through Microsoft Entra ID.


Microsoft Entra ID as the Human Identity Provider

I configured OpenBao’s OIDC authentication method against Microsoft Entra ID.

The resulting authentication path is:

1
2
3
4
5
6
7
8
9
10
11
Engineer
   │
   ▼
Microsoft Entra ID
   │
   │ MFA / Conditional Access capable identity plane
   ▼
OIDC Token
   │
   ▼
OpenBao Identity

This provides several advantages over locally managed OpenBao accounts or routine root-token usage:

  • centralized identity lifecycle;
  • MFA support;
  • Conditional Access support;
  • account disablement from Entra;
  • group-based assignment;
  • better attribution.

An OIDC Problem That Actually Mattered

One of the useful implementation problems appeared immediately after configuring OIDC.

OpenBao returned:

1
2
failed to fetch groups:
"groups" claim not found in token

The OpenBao OIDC role expected a claim similar to:

1
groups

but Microsoft Entra ID was not emitting the required group membership information.

The fix was not an OpenBao policy change.

The token itself needed to contain the expected claim.

In Entra, I configured the application token settings to emit group claims and restricted them to groups relevant to the application instead of blindly returning all of the user’s memberships.

The flow became:

1
2
3
4
5
6
7
8
9
10
11
12
13
Entra User
   │
   ▼
Assigned Entra Group
   │
   ▼
OIDC groups claim
   │
   ▼
OpenBao identity mapping
   │
   ▼
OpenBao policy

This reinforced an important identity lesson:

Authorization logic can only be as reliable as the claims entering the authorization system.


Authentication vs Authorization

OIDC authenticates the user.

It does not directly give that user permission to secrets.

Once the user is authenticated, OpenBao Identity maps the authenticated identity into internal entities and groups.

Policies then determine what the identity can access.

That distinction makes the design significantly cleaner:

1
2
3
4
5
6
7
8
9
10
11
Entra ID
   │
   └── proves identity

OpenBao Identity
   │
   └── maps identity context

OpenBao Policy
   │
   └── authorizes resource access

Administrators and engineers therefore do not share the same authorization model.


Project Isolation with Templated Policies

I wanted project isolation without creating an entirely separate copy of every policy.

OpenBao’s identity-aware policy templating solved this cleanly.

Each relevant entity contains project metadata.

For example:

1
2
3
4
5
Project A:
project = ca

Project B:
project = defender

A shared policy can then reference:

1

A simplified KV v2 policy looks like this:

1
2
3
4
5
6
7
path "intern-secrets/data//*" {
  capabilities = ["create", "read", "update"]
}

path "intern-secrets/metadata//*" {
  capabilities = ["read", "list"]
}

The same policy therefore becomes different effective access depending on the authenticated entity.

For the ca project:

1
intern-secrets/ca/*

For the defender project:

1
intern-secrets/defender/*

This creates horizontal isolation:

1
2
CA project      X      Defender secrets
Defender project X     CA secrets

without duplicating the policy architecture.


Vertical Separation

Horizontal project isolation is only half the model.

The platform also separates users from infrastructure administration.

A normal project identity does not receive access to:

1
2
3
4
5
6
7
8
root policy
sudo capabilities
sys/auth/*
sys/policies/*
sys/mounts/*
pki-root/*
unseal keys
initial root token

The model is effectively:

1
2
3
4
5
6
7
8
9
10
11
12
13
Platform Administrator
├── Authentication configuration
├── Identity mapping
├── Policies
├── Secrets engines
├── PKI issuers
├── Audit
└── Recovery

Project Engineer
├── Own project secrets
├── Own project certificate role
└── Normal self-service operations

This is both horizontal separation and vertical separation.


KV v2 Secrets Architecture

A dedicated KV v2 secrets engine stores application credentials.

The logical structure looks like:

1
2
3
4
5
6
7
8
9
10
intern-secrets/
├── ca/
│   └── graph/
│       ├── client-secret
│       └── certificate
│
└── defender/
    └── graph/
        ├── client-secret
        └── certificate

A client-secret record can contain:

1
2
3
4
tenant_id
client_id
client_secret
expires_on

Certificate metadata can contain:

1
2
3
4
5
6
tenant_id
client_id
thumbprint
subject
serial_number
expires_on

The important change is architectural.

Instead of:

1
$ClientSecret = "something-sensitive"

inside a script or configuration file, the intended model becomes:

1
2
3
4
5
6
7
8
9
Authenticate to OpenBao
        ↓
Authorize project path
        ↓
Retrieve required credential
        ↓
Use credential in process memory
        ↓
Authenticate workload

The repository does not need to contain the credential.


Why I Did Not Treat OpenBao as a Password Database

Centralization is useful, but simply moving every secret into OpenBao does not automatically make an architecture good.

The more important questions are:

1
2
3
4
5
6
7
Who can retrieve it?
Why can they retrieve it?
For which project?
For how long?
What can the credential do after retrieval?
Can its use be audited?
Can it be rotated?

That is why the OpenBao implementation combines:

1
2
3
4
5
6
7
8
9
Identity
+
Policy
+
KV
+
PKI
+
Audit

rather than using KV alone.


PKI Architecture

The second major OpenBao function is internal PKI.

Instead of allowing every project to independently create and manage arbitrary self-signed authentication certificates, I created a simple two-tier CA hierarchy.

1
2
3
4
5
6
7
8
9
Graph Authentication Root CA
             │
             │ signs
             ▼
Graph Authentication Intermediate CA
             │
             │ issues
             ▼
Application Authentication Certificates

The corresponding OpenBao PKI mounts are conceptually:

1
2
pki-root/
pki-graph/

The Root CA exists primarily as the trust anchor.

Routine application certificate issuance happens through the Intermediate CA.


Why Separate Root and Intermediate CAs?

Technically, the Root CA could issue every application certificate.

I intentionally avoided that model.

The better hierarchy is:

1
2
3
4
5
6
7
8
9
Root CA
   │
   └── signs Intermediate

Intermediate CA
   │
   ├── signs Project Certificate A
   ├── signs Project Certificate B
   └── signs future workload certificates

This limits routine exposure of the root signing capability.

The Root CA is therefore an administrative resource, not a self-service resource.


PKI Roles as an Authorization Boundary

Having an Intermediate CA is not enough.

If every project can request any certificate identity it wants, the PKI still has a weak authorization model.

I therefore created project-specific certificate roles.

Conceptually:

1
2
graph-ca
graph-defender

The roles restrict issuance characteristics such as:

  • allowed certificate identity;
  • key algorithm;
  • key size;
  • validity;
  • key usage;
  • certificate usage;
  • wildcard behavior.

Example project identities are structured similarly to:

1
2
ca.graph-auth.home.arpa
defender.graph-auth.home.arpa

The policy can then use the same project metadata pattern:

1
2
3
4
5
6
7
path "pki-graph/sign/graph-" {
  capabilities = ["create", "update"]
}

path "pki-graph/issue/graph-" {
  capabilities = ["create", "update"]
}

This means the same shared policy dynamically resolves to the correct certificate role.

1
2
3
4
5
6
7
project=ca
    ↓
graph-ca

project=defender
    ↓
graph-defender

A user from one project cannot simply use the other project’s role.


Two Certificate Issuance Models

OpenBao supports two relevant workflows.

Model 1 — OpenBao Generates the Key

1
2
3
4
5
6
7
Engineer
   │
   ▼
OpenBao PKI issue endpoint
   │
   ├── generates private key
   └── generates certificate

This is operationally convenient.

But the private key is returned during issuance.

For long-lived workload authentication, that was not my preferred security model.


Model 2 — Local Key + CSR Signing

The preferred workflow is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Windows Host
   │
   ├── Generate non-exportable private key
   │
   └── Generate PKCS#10 CSR
   │
   ▼
OpenBao
   │
   └── Sign CSR through authorized PKI role
   │
   ▼
Signed Certificate
   │
   ▼
Windows Certificate Store

The critical property is:

The private key never has to leave the endpoint that uses it.

OpenBao acts as the certificate issuer rather than the private-key repository.

That difference matters.


End-to-End Certificate Authentication Flow

The resulting Microsoft Graph certificate workflow is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. Generate private key locally
          ↓
2. Generate CSR
          ↓
3. Submit CSR to OpenBao
          ↓
4. OpenBao validates assigned PKI role
          ↓
5. Intermediate CA signs certificate
          ↓
6. Install certificate chain
          ↓
7. Bind signed leaf certificate to local private key
          ↓
8. Upload PUBLIC leaf certificate to Entra App Registration
          ↓
9. Keep private key local
          ↓
10. Authenticate application to Microsoft Graph

Only the public certificate is registered with Microsoft Entra ID.

The private key remains on the machine performing the workload authentication.


Windows Certificate Store

For the certificate-based workflow, the certificate chain can be installed into the normal Windows certificate stores.

Conceptually:

1
2
3
4
5
6
7
8
9
Trusted Root Certification Authorities
└── Graph Authentication Root CA

Intermediate Certification Authorities
└── Graph Authentication Intermediate CA

Personal / My
└── Application Authentication Certificate
       └── Private key

The application authentication private key can be created as non-exportable.

This provides a much stronger model than distributing .pfx files between machines.


Microsoft Graph App-Only Authentication

Once the public certificate is registered on the Entra application, Microsoft Graph PowerShell can authenticate using the application identity.

A simplified example looks like:

1
2
3
4
5
6
7
8
9
10
$TenantId  = "<tenant-id-from-authorized-metadata>"
$ClientId  = "<client-id-from-authorized-metadata>"
$Thumbprint = "<certificate-thumbprint>"

Connect-MgGraph `
    -TenantId $TenantId `
    -ClientId $ClientId `
    -CertificateThumbprint $Thumbprint

Get-MgContext

A successful context should indicate:

1
AuthType = AppOnly

At this point, OpenBao is no longer deciding what Microsoft Graph allows.

The token’s effective Microsoft Graph privilege comes from the application permissions granted and consented to on the Entra application registration.

That distinction is essential.


Least-Privilege Microsoft Graph Applications

Each project receives its own application registration rather than sharing one generic privileged application.

Conceptually:

1
2
3
4
5
6
7
Conditional Access Analyzer
        │
        └── only required Graph permissions

Defender Investigation Tool
        │
        └── only required Defender / Graph permissions

This gives the architecture two independent least-privilege boundaries.

Credential boundary

1
2
OpenBao
→ Who can retrieve or use the credential?

Cloud authorization boundary

1
2
Microsoft Entra ID
→ What can the application actually do?

Both have to be correct.


Client Secrets Still Have a Place — Temporarily

The platform also supports client-secret authentication.

The flow is:

1
2
3
4
5
6
7
8
9
10
11
12
Entra App Registration
        │
        └── Client Secret
                │
                ▼
         OpenBao KV v2
                │
                ▼
         Authorized Project
                │
                ▼
         App-only Graph Auth

This is useful for compatibility and transition.

But for persistent workloads, I prefer certificate-based authentication over maintaining long-lived password-style application credentials where certificate or stronger workload identity options are practical.


Human Identity != Workload Identity

One architectural decision deserves repeating.

An engineer may authenticate to OpenBao through:

1
2
3
Microsoft Entra ID
+
OIDC

but the engineering application itself should not depend on a human OIDC session forever.

Automated workloads need their own workload authentication model.

1
2
3
Human authentication
             ≠
Workload authentication

This makes lifecycle, revocation and auditing much cleaner.


Auditability

OpenBao audit logging is enabled.

The file audit device captures request/response activity for audited OpenBao operations.

That provides visibility into questions such as:

1
2
3
4
5
6
Who authenticated?
Which secret path was accessed?
Which PKI role was used?
Was a certificate requested?
Was access denied?
Was a secret read?

The audit data itself is security-sensitive and is protected accordingly.

I also treat audit reliability as part of the platform rather than as optional logging added afterward.


Backup and Disaster Recovery

A secrets platform without a recovery strategy is a future outage.

Because the platform uses Raft integrated storage, the recovery approach is based around Raft snapshots rather than copying live container directories and assuming the result is consistent.

The lifecycle is conceptually:

1
2
3
4
5
6
7
8
9
10
11
12
13
OpenBao
   │
   ▼
Raft snapshot
   │
   ▼
Encrypted backup
   │
   ▼
Off-host protected storage
   │
   ▼
Periodic restore validation

Recovery planning also has to account for:

  • OpenBao configuration;
  • TLS material;
  • DNS configuration;
  • OIDC configuration documentation;
  • unseal shares;
  • CA lifecycle information.

Recovery secrets should not simply be bundled together with the data they protect.


Certificate Lifecycle

Certificate authentication moves the problem from:

1
"Where is the password?"

to:

1
"How do I govern the certificate lifecycle?"

That is a better problem, but it is still a problem.

For every workload certificate, I want to be able to identify:

1
2
3
4
5
6
7
8
9
Serial number
Thumbprint
Subject
Issue date
Expiration
Associated Entra application
Project owner
OpenBao issuer
OpenBao role

The renewal process should eventually become:

1
2
3
4
5
6
7
8
9
10
11
Issue replacement certificate
        ↓
Register public certificate in Entra
        ↓
Validate new authentication
        ↓
Move workload to new certificate
        ↓
Remove old Entra credential
        ↓
Revoke / retire previous certificate

Credential rollover should happen before expiration, not after an application stops working.


Validation

I did not consider the platform complete merely because OpenBao displayed:

1
2
Initialized: true
Sealed: false

The security boundaries needed to be tested.

The validation included several layers.

OIDC Authentication

Validated that intended users could authenticate using Microsoft Entra ID.

1
2
3
4
5
6
7
Entra Login
    ↓
OIDC
    ↓
OpenBao Identity
    ↓
Expected policy

Negative Authorization Testing

A project should not only be able to access what it needs.

It must also fail to access what it does not need.

I validated separation around:

1
2
3
4
5
Project A → Project A secrets     PASS
Project A → Project B secrets     DENIED

Project A → Project A PKI role    PASS
Project A → Project B PKI role    DENIED

Negative tests are often more valuable than successful login tests when validating least privilege.


Client-Secret Authentication

Validated:

1
2
3
4
5
6
7
OpenBao authentication
        ↓
Authorized KV read
        ↓
Credential retrieval
        ↓
Microsoft Graph app-only authentication

Certificate Authentication

Validated:

1
2
3
4
5
6
7
8
9
10
11
12
13
Local key generation
        ↓
CSR
        ↓
OpenBao signing
        ↓
Certificate installation
        ↓
Public certificate registration in Entra
        ↓
Microsoft Graph certificate authentication
        ↓
App-only context

Both client-secret and certificate-based Microsoft Graph authentication were therefore exercised end to end.


Security Controls Summary

The final architecture applies several controls at different layers.

Security ObjectiveControl
Human authenticationMicrosoft Entra ID OIDC
MFA / CA capabilityMicrosoft Entra identity plane
Project isolationOpenBao Identity metadata + policies
Secret isolationKV v2 project paths
Certificate isolationProject-specific PKI roles
Private-key protectionLocal non-exportable key + CSR
CA protectionRoot / Intermediate separation
Graph privilegePer-application least-privilege permissions
TraceabilityOpenBao audit logs + Entra logs
RecoveryRaft snapshots + protected recovery material
Credential lifecycleExpiring secrets/certificates + planned rotation
Administrative separationAdmin and project policies kept separate

The important part is that no single control is treated as sufficient.


A Security Boundary I Nearly Underestimated

One of the most valuable conclusions from this project was that:

Secure credential storage and least-privilege authorization are not the same thing.

It is possible to build an extremely secure secret store around a dangerously privileged Entra application.

It is also possible to have a perfectly least-privileged application whose private key is sitting unprotected on a workstation.

The security model therefore has to evaluate both:

1
2
3
Credential Security
        +
Authorization Security

This project forced me to work across both sides of that boundary.


What I Would Change for a Larger Production Deployment

This platform is designed for a controlled internal engineering environment.

If I were scaling the same architecture into a larger production environment, I would extend several areas.

Multi-node OpenBao

The current environment values simplicity.

A production deployment requiring higher availability should use multiple OpenBao nodes with Raft replication and an appropriately designed failure domain.


Stronger Unseal Strategy

Manual Shamir unseal is appropriate for learning and controlled environments.

Depending on production requirements, I would evaluate HSM/KMS-backed or other automated unseal designs with stronger operational controls.


Redundant Audit Destinations

The current file audit model provides local auditability.

For production I would also evaluate:

1
2
3
4
5
OpenBao Audit
      ↓
Multiple audit devices
      ↓
Central security monitoring / SIEM

This reduces reliance on one local audit destination.


Automated Certificate Rotation

The current workflow proves the entire lifecycle.

The next engineering step would be automation around:

  • expiry discovery;
  • CSR creation;
  • renewal;
  • Entra credential registration;
  • validation;
  • old-certificate retirement.

Eliminate Client Secrets Where Practical

Client secrets remain useful for compatibility.

The stronger target is:

1
2
3
4
5
Managed Identity
or
Federated Workload Identity
or
Certificate

depending on where the workload actually runs.

Because this environment is locally hosted rather than Azure-native, certificate-based authentication is a practical fit.


Formal Recovery Testing

Having a backup is not the same as having a recovery capability.

A more mature operational model would include scheduled restore exercises with documented recovery objectives.


What This Project Taught Me

This project started as:

“I don’t want application secrets sitting on developer machines.”

It became much more interesting than that.

I had to work through:

  • OIDC claims;
  • Entra application configuration;
  • identity mapping;
  • RBAC;
  • templated authorization;
  • secrets-engine design;
  • PKI hierarchy design;
  • certificate issuance;
  • CSR workflows;
  • Windows certificate stores;
  • app-only OAuth;
  • Microsoft Graph permissions;
  • container networking;
  • internal DNS;
  • TLS;
  • audit logging;
  • secret recovery;
  • credential lifecycle management.

More importantly, it connected those technologies into one security model.

The main lesson is:

Security architecture is usually less about choosing one secure product and more about defining clear trust boundaries between several systems.


Engineering Skills Demonstrated

From a technical perspective, this project touches several areas I work with regularly.

Microsoft Identity

1
2
3
4
5
6
Microsoft Entra ID
OIDC
App Registrations
Service Principals
Application Permissions
App-only Authentication

Security Engineering

1
2
3
4
5
6
7
Least Privilege
Separation of Duties
Credential Isolation
Trust Boundaries
RBAC
Auditability
Credential Lifecycle

PKI

1
2
3
4
5
6
7
8
X.509
Root CA
Intermediate CA
CSR
Certificate Signing
Certificate Stores
Private-Key Protection
Certificate Rotation

Automation

1
2
3
4
5
PowerShell
Microsoft Graph PowerShell SDK
Microsoft Graph API
Certificate Authentication
Secret Retrieval

Infrastructure

1
2
3
4
5
6
7
8
Proxmox VE
Ubuntu Linux
Docker
Docker Compose
DNS
TLS
Reverse Proxying
Private Networking

Secrets Management

1
2
3
4
5
6
7
8
OpenBao
Integrated Storage / Raft
KV v2
OIDC
Identity
Templated Policies
PKI Secrets Engine
Audit Devices

Final Architecture

The final design can be summarized as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
                           HUMAN PLANE

                      Microsoft Entra ID
                             │
                             │ OIDC
                             ▼
                         OpenBao
                 ┌───────────┼───────────┐
                 │           │           │
              Identity      KV v2       PKI
                 │           │           │
                 │      Project Secrets │
                 │                       │
                 └──── Project RBAC ─────┘


                         WORKLOAD PLANE

                     Security Project
                            │
                            │
                    Certificate / Secret
                            │
                            ▼
                  Entra App Registration
                            │
                   Application Permissions
                            │
                            ▼
                     Microsoft Graph


                      INFRASTRUCTURE PLANE

                       Proxmox VE
                            │
                      Ubuntu Server
                            │
                          Docker
                            │
             DNS / TLS / OpenBao / Git / Network

Each plane has a different responsibility.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Entra OIDC
→ Who is the human?

OpenBao
→ Which credential may they access?

PKI
→ Which certificate may they obtain?

Entra App Registration
→ Which workload identity are they using?

Microsoft Graph permissions
→ What can that workload identity do?

That separation is the core of the architecture.


Conclusion

What started as a way to avoid storing application secrets on individual machines became a practical exercise in identity, PKI, secrets management, authorization, infrastructure and Microsoft Graph security.

The final platform provides:

  • centralized secrets management;
  • internal certificate issuance;
  • Microsoft Entra OIDC authentication;
  • project-scoped authorization;
  • Root/Intermediate CA separation;
  • non-exportable private-key workflows;
  • certificate-based Microsoft Graph authentication;
  • auditable secret and PKI operations;
  • per-project application identities;
  • a defined credential lifecycle.

The part I value most is not that OpenBao is running.

It is that the environment now has explicit answers to questions such as:

Who can access this credential?

Which project owns it?

Which certificate may that identity request?

Where does the private key live?

What Microsoft Graph permissions does the workload actually have?

Can the operation be audited?

How is the credential rotated or revoked?

Those are the questions that turn a collection of infrastructure services into a security architecture.


References

The implementation and design were validated against official documentation covering:

  • OpenBao Integrated Storage / Raft;
  • OpenBao Identity and templated policies;
  • OpenBao PKI secrets engine;
  • OpenBao audit devices;
  • Microsoft Entra ID OIDC and group claims;
  • Microsoft Entra application credentials;
  • Microsoft Graph app-only authentication;
  • Microsoft Graph PowerShell certificate authentication;
  • Microsoft identity platform application credential security guidance.
This post is licensed under CC BY 4.0 by the author.