Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Privileged Access Management Lab

A working PAM environment built from free components: a credential vault that stores and rotates privileged accounts under a policy, and a session proxy that brokers and records every SSH and RDP session to two isolated target machines.

Session playback


The business problem

An organization has servers that a handful of people need administrative access to. That creates two separate problems, and most teams only solve the first one.

Problem one is the credential. The root password is in a password manager, a runbook, a config file, and three people's heads. Nobody knows when it last changed. When an admin leaves, the honest answer to "what did they have access to" is a spreadsheet somebody stopped updating in 2023. An application needs that same password, so it sits in a connection string in source control.

Problem two is the session. Even if the credential is controlled, once an admin is on the box there is no record of what they did. When something breaks at 3 a.m. and the change log is empty, the investigation starts with asking people what they remember.

Auditors ask about both. SOX, PCI DSS, HIPAA, and cyber insurance renewals all want the same two things: proof that privileged credentials are controlled and rotated, and proof that privileged sessions are recorded and attributable.

This lab builds both halves and produces evidence for each.


Architecture

flowchart LR
    subgraph host["Windows host"]
        admin["Operator<br/>(browser)"]
    end

    subgraph vault["Vault stack :8080"]
        nginx["nginx<br/>TLS proxy"]
        conjur["Conjur OSS<br/>Digital Vault"]
        pg1[("PostgreSQL<br/>encrypted store")]
        nginx --> conjur --> pg1
    end

    subgraph scripts["Python components"]
        ccp["ccp_retriever.py<br/>reads only"]
        cpm["cpm_rotator.py<br/>reads and rotates"]
    end

    subgraph proxy["Proxy stack :8081"]
        guac["Guacamole<br/>web app"]
        guacd["guacd<br/>session daemon"]
        pg2[("PostgreSQL<br/>connections, history")]
        rec[/"recordings<br/>volume"/]
        guac --> guacd
        guac --> pg2
        guacd -->|writes| rec
        rec -->|read only| guac
    end

    subgraph targets["Isolated network 192.168.148.0/24 (no gateway)"]
        linux["LabLinux<br/>192.168.148.10<br/>SSH"]
        win["LabWindows<br/>192.168.148.20<br/>RDP"]
    end

    admin --> guac
    guacd --> linux
    guacd --> win
    ccp --> conjur
    cpm --> conjur
Loading
Lab component CyberArk equivalent Role
Conjur OSS Digital Vault Encrypted credential storage
pam-policy.yml Safe and Safe permissions Access control as code
Conjur variable Privileged account A stored credential
Conjur host Application or machine identity Non-human account
ccp_retriever.py Central Credential Provider Runtime credential retrieval
cpm_rotator.py Central Policy Manager Automated rotation
Apache Guacamole Privileged Session Manager Session brokering and recording

These are analogies, not equivalences. See Honest limits.


What this proves

Access control is code, not a checkbox. pam-policy.yml defines two machine identities against the same two credentials. cpm-service gets read, execute, update. app-server gets read, execute. One word of difference.

The boundary holds under test. Running the rotation script as app-server authenticates successfully, reads the current credential successfully, and is refused at the write. The boundary is not access, it is the update privilege, and it is evaluated at the moment of the call rather than at login.

RBAC boundary

Rotation propagates without coordination. The rotator generates a new 20-character credential and writes it to the vault. The retriever, a separate process authenticating on its own, returns the new value. Nothing told it the password changed.

The targets cannot reach the internet. Not "traffic is blocked." There is no default route at all, so the kernel has nowhere to send a packet destined off-subnet.

Target isolation

Every session is recorded and attributable. Each session writes to its own directory named for its history UUID, which is how a recording ties back to a specific user, connection, and timestamp. Playback shows the session alongside a timestamped keystroke log and an activity histogram.

Session playback


Repository contents

policy/pam-policy.yml               Access control policy, the Safe defined as code
scripts/ccp_retriever.py            Runtime credential retrieval
scripts/cpm_rotator.py              Credential rotation
docker/guacamole/docker-compose.yml Session proxy stack
RUNBOOK.md                          Full build, start to finish
BUILD-LOG.md                        What broke and what fixed it
HOW-I-USED-AI.md                    Where AI was used and where it was not
docs/images/                        Evidence captures
docs/media/                         Session playback

RUNBOOK.md is the complete build. Every command, every capture, and every place the source material was wrong for this hardware.

BUILD-LOG.md is the failures. A hypervisor conflict inherited from an earlier project, a data key broken by Windows line endings, a schema silently corrupted by PowerShell redirection, and recordings that wrote perfectly while the web app was locked out of its own directory.


Honest limits

Every one of these is something you would not do in production. They are listed because a lab that hides its shortcuts is not evidence of anything.

  • The two halves are not integrated. Guacamole stores target credentials in its own database rather than pulling them from Conjur at connect time. This is the most significant gap between this lab and a real PAM deployment, and it is the obvious v2.
  • guacadmin / guacadmin left as the default admin.
  • Database passwords in plaintext in docker-compose.yml.
  • Ignore server certificate on the RDP connection.
  • Both Python scripts talk plain HTTP to localhost:8080, bypassing the nginx TLS proxy.
  • Recording directories made world-readable with chmod o+rX. The correct fix is a shared group between the two containers.
  • Single-node everything. No HA, no replication, no backup, no HSM. A real Digital Vault is a hardened appliance; this is one container.
  • The rotator changes the vault value only. A real CPM also changes the account on the target system through a plugin framework. That framework is most of what CyberArk sells.

CyberArk PAS binaries are behind a customer portal. This is not CyberArk. It is the same architecture built from components that behave the same way mechanically, which is a different and smaller claim.


Running it

Full instructions in RUNBOOK.md. Requirements: Docker Desktop, VirtualBox 7, Python 3.11+, roughly 40 GB of disk, and a machine that can run two VMs alongside Docker.

Note that Docker Desktop and VirtualBox both want the hypervisor layer. Phase 0 of the runbook measures whether they coexist on your hardware before anything gets built, because finding out later means rebuilding.


Credits

Built following NextWork lab material, with divergences documented in the runbook where the source was wrong for this hardware or omitted a check worth keeping.

About

A working privileged access management lab. Conjur OSS as the credential vault with policy-based rotation and a tested RBAC boundary, and Apache Guacamole as a session proxy recording every SSH and RDP session to two isolated targets.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages