Sprout

The Ghost in the Compliance Policy

Tom Hanks! What Are You Doing in my Bed?

DING

You've got mail

-- Nita's phone, as her love of romantic comedy escaped her ability to procure a good night's rest

"Go away Tom, it's 2am. Now is not the time for love", Nita thought as the phone on the stand beside her bed chimed again.

DING

"I could have sworn I turned that to silent before I went to bed. Sorry", she said to her cat Mutex who now lay awake beside her. The look on the cat's face was one worn often by those who spend too much time around infrastructure, a mix of annoyance and care as he knew his own infrastructure was about to get out of bed.

** KRM-1 : Deployment Pipeline Needed **

Submitted By: probably.ai@infinity
Assigned To: nita@ininity
Ticket #: KRM-1

Nita,

I apologize for disturbing your rest, but I am in need of your immediate assistance.  I submitted the CHG 
documentation to the review board ahead of this morning's scheduled production release and they have 
rejected it stating that it does not meet the company standards for controls placed on our regulated industry.

They were unable to tell me which controls were missing,  so we will need to implement all of them.  I have 
attached a table containing a matrix for your review.

It is imperative that we meet our deadline for this morning's release.  Bob was very angry about CHG and 
I need to get this release completed so I can get off this project.

Best Regards (and thank you),

Alice

Nita's initial rush of emotion had softened slightly, something about the Alice's tone as encoded in her choice of words had caused Nita to actually consider Alice's request. "How bad could it be?", she said to herself quietly. Mutex had curled up asleep again on the pillow he kept beside her head at night and she felt she'd put up with enough for both of them for one night.

She opened up her laptop so she could review the attached document on a surface slightly larger than her phone's screen. Her previous life hadn't had much need for rigid enforcement of controls. The platform her old boss had put together tracked the lifecycle of an application build as an immutable image, with its own context tracking when and where it had been deployed. The original integration seeded that context with its bonafides - the cryptographic hashes of the source used to create it - and each subsequent pipeline enriched the account of its life with their own stamps.

She felt the work she'd completed the previous afternoon was a good foundation for any compliance program, and given the late hour she really hoped a few tweaks were all that was to be required. "After all", she thought, "how bad could compliance be?"

Kremis Compliance Controls :: Functional Capability Matrix and Gap Analysis

After our extensive analysis, we have charted what Kremis currently has or needs, mapped across all frameworks:
Pipeline Capability Part 11 Annex 11 GAMP 5 SOX HIPAA ICH CSA Kremis Status
Immutable audit trail §11.10(e) Sec 9 Core ITGC §164.312(b) Q7 5.46 Evidence Tracing module
E-signatures on approvals §11.50+ Sec 14 OQ Sign-off §164.312(d) Needed
Approval gates §11.10(f) Sec 10 V-model Change mgmt Q10 3.2.1 Risk gates Needed
Segregation of duties §11.10(d,g) Sec 12 Risk ctrl Core SOX §164.312(a) Needed
Artifact integrity §11.10(c) Sec 5, 7 Data integrity §164.312(c) Q7 5.46 Needed
Full traceability §11.10(e) Sec 4.1 Trace matrix SDLC evidence Q10 3.2.4 Leverage records Context accumulation
RBAC §11.10(d) Sec 12.1 Risk ctrl Logical access §164.312(a)(1) Needed
Change control on config §11.10(k) Sec 10 Change SOP Change mgmt Q7 5.44 Config mgmt Git (implicit)
Automated test gates §11.10(a) Sec 4.5 OQ SDLC Q9 Primary activity Needed
Context accumulation §11.10(e) Sec 4.1 Knowledge mgmt Audit evidence §164.312(b) Q10 3.2.4 Evidence Built in
Rollback Sec 16 Risk mitigation Q9 Needed
Env segregation §11.10(d) Sec 4 V-model envs ITGC Q7 5.40 Needed
Encrypted comms §164.312(e)(1) Needed
Retention/archival §11.10(c) Sec 17 Data lifecycle Records §164.312(a) Q7 5.46 Needed
Please do the needful at your earliest convenience.

BR,

Alice

Nita went to boil a kettle.

When One is Truly and Soundly Beaten, One Must Get on With It

Cuppa in hand, she settled down at her desk and opened her laptop. She'd not had the pleasure of having to spend much time with compliance at her last gig. Once in a while one team or another would open a ticket, requesting assistance with security audits or other vendor driven theatre but this looked more interesting. As her eyes scanned the page, she started to mentally categorize the required capabilities into functional areas that Kremis was lacking:

"Then", she thought efficiently, "I'll be able to implement the necessary gates". The remaining items on the list were core platform features she felt, "something Kremis should be doing anyways", she whispered quietly to the duck she pulled out of the purse near the floor by her desk. She'd bought it at the bodega near the taco place near her office, the gentle weight of it in her hand reminding her of the gentle pressure she'd felt lower down after the tacos she'd had for lunch that day. "Maybe something with a few more vegetables tomorrow" she said to the duck as she placed it beside her keyboard where it grinned its big billy grin up at her ready to resume its role as interlocuter in her tasks.

She knew she'd need to work out the environment model at some point, she just needed to figure out where more basic things where first like which cloud they were on or where the data centre might be. She had a deployment due in the morning and she still wasn't quite sure where she was to be pointing her pipeline. "Hopefully Alice knows" she said to the duck, returning her thoughts to compliance.

The Key to Her Heart

Looking back on her trifecta of functional components, she knew she needed to start with at least basic signing infrastructure. Public key infrastructure she knew as a matter of encoding trust in a form that everyone could agree on. "Or at least well enough to exchange money over", she thought as she opened a terminal, "which is good enough for an application called infinite-money". She entered a simple command to generate a key pair she could use for testing, for production she'd need a key issued from the central Certificate Authority that was authorized to issued idenities for Infinity Co. She'd add finding someone who knew where that might be to her list of known unknowns.

Generating public/private ed25519 key pair. Enter file in which to save the key (/home/nita/.ssh/id_ed25519): /tmp/test Enter passphrase for "/tmp/test" (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /tmp/test Your public key has been saved in /tmp/test.pub The key fingerprint is: SHA256:7oSmeDn1ZU/B8yhBYbqFBg0Pg9urK7Fs/1sT+vKLgi8 nita@infinity The key's randomart image is: +--[ED25519 256]--+ | .=o o. | | . =.+. | | o =... | | . .. o. + | | .S . = | | . o+ .+ o . | | . + ++.=o + | | E.*o.*.. . | | ..*==++=. | +----[SHA256]-----+

The cutover to ed25519 as the standard for AmazonLinux AMIs had been a bit of a pain at her old job she remembered. The curse of a platform engineer was always in the maintenance cycle and she'd started the job around the time that the old RSA standard had gotten a bit too long in the teeth for the securiry conscious. The platform at her old company was called Peregrine and it had grown tendrils all over the organization's delivery practices. She remembered many long weeks of chasing old JSON formatted Packer templates and upgrading them to HCL as one of her first tasks after on-boarding. It seemed that the version of Packer that had become standardized at the company was old enough that the version of the EC2 plugin that was required for ed25519 support needed to be installed via plugin upgrade and that required an HCL template to declare the dependency. "Luckily I got away just in time", she said to the duck as her heart started to pound in her chest. Her old boss had held the line as long as he could but a final volley from an unexpected direction - the migration of Hashicorp's binary downloads from Github to their own artifact repository - had killed off any hope of maintaining peaceful compatibility forever. The plugin installations could not complete without finally upgrading the packer binary version but the version required to support the new download location discontinued support for the chef plugin that all AMI based deployments used. When she'd left there were two warring camps:

Nita checked the contents of her keys, numbers encoded as character strings always soothed her, the promise of pattern enough to distract her from her past worry.

-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACDm1V9I/hZ/OHVYh/RYZ/LB/fT5ZwHXYeoaQQ20bsrvfgAAAJgMF47FDBeO xQAAAAtzc2gtZWQyNTUxOQAAACDm1V9I/hZ/OHVYh/RYZ/LB/fT5ZwHXYeoaQQ20bsrvfg AAAECHDkMhdWK2MNI6lcDvq9QmzWQ9RLwY5OMy9O7OJ0nX/+bVX0j+Fn84dViH9Fhn8sH9 9PlnAddh6hpBDbRuyu9+AAAADmdmYXdjZXR0QHBhdHR5AQIDBAUGBw== -----END OPENSSH PRIVATE KEY----- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIObVX0j+Fn84dViH9Fhn8sH99PlnAddh6hpBDbRuyu9+ nita@infinity

For Kremis, she thought she'd assign each of her existing Actors an identify through the use of a key assigned to it from a registry. She'd implement a simple Local version of a Backend interface that could be extended to consume whatever secrets store was available at Infinity Co, a division of Zero Industries.

    class Backend
      def generate(name, algorithm: :rsa)
        raise NotImplementedError
      end
```[id=the-ghost-in-the-compliance-policy-the-key-to-her-heart-fence-0]

For testing, she decided to feed her `Local` keystore from a simple YAML file, as you do.

## Identity, as a Provider

"Keys only ensure something remains the way it was said", she thought.  A key could be used to verify the provenance of an artifact, but without an identity to tie it to the truth floated in a self-coherent bundle of logic.  The idea of identity was one that had long consumed Nita's thoughts in various forms.

For her current crisis, she reviewed the requirements from Alice's details gap analysis that required an identity to close:

| Pipeline Capability | Part 11 | Annex 11 | GAMP 5 | SOX | HIPAA | ICH | CSA | Kremis Status |
|---|---|---|---|---|---|---|---|---|
| E-signatures on approvals | §11.50+ | Sec 14 | OQ | Sign-off | §164.312(d) | — | — | Needed |
| Approval gates | §11.10(f) | Sec 10 | V-model | Change mgmt | — | Q10 3.2.1 | Risk gates | Needed |
| Segregation of duties | §11.10(d,g) | Sec 12 | Risk ctrl | Core SOX | §164.312(a) | — | — | Needed |
| RBAC | §11.10(d) | Sec 12.1 | Risk ctrl | Logical access | §164.312(a)(1) | — | — | Needed |

Each of these items required the ability to draw a distinction:

- **E-signatures** requires the distinction between something that is unapproved and something that is approved through the manual application of human identity as a stamp of quality
- **Approval gates** requires the distinction between something that is unapproved and something that is approved through the automated application of machine identity as a stamp of quality
- **Segregration of duties** requires the distinction both of the duties and of the agents to perform them
- **RBAC** requires the distinction of those assigned to a role and those who are not, along with the distinction of who can do what through enumerated distinct actions

She thought she could encapsulate the idea of identity into a single object.

```vim[id=the-ghost-in-the-compliance-policy-identity-as-a-provider-fence-0]
  class Identity
    attr_reader :id, :name, :roles, :type, :code_version,
                :public_key, :private_key, :token, :scopes

    def initialize(id:, name:, roles:, type: :human, code_version: nil,
                   public_key: nil, private_key: nil, token: nil, scopes: [])
      @id = id
      @name = name
      @roles = Array(roles).map(&:to_sym)
      @type = type.to_sym
      @code_version = code_version
      @public_key = public_key
      @private_key = private_key
      @token = token
      @scopes = Array(scopes).map(&:to_sym)
    end

    def human?
      @type == :human
    end

    def service?
      @type == :service
    end

    def scope?(scope)
      @scopes.include?(scope.to_sym)
    end

    # Claims hash for JWT payload
    def claims
      c = {
        sub: @id,
        name: @name,
        roles: @roles.map(&:to_s),
        type: @type.to_s,
        scope: @scopes.map(&:to_s).join(" ")
      }
      c[:code_version] = @code_version if @code_version
      c
    end

    def to_h
      claims.merge(public_key: @public_key&.to_pem)
    end

    def inspect
      "#<Kremis::Identity #{@id} roles=#{@roles} scopes=#{@scopes}>"
    end
  end

The simplest way to pass identity around was through JWT, which is just a standard way of encrypting metadata in a self-referential package. "Well, signing not encrypting", she corrected herself. Picking up the duck, she continued "the contents are recoverable, otherwise they'd be useless in this context. Signing just means that we can guarantee that the scopes are the same at the time that we read them as they were when they were written".

Kremis was built in Ruby and the Rails ecosystem ensured that the JWT standard was well covered.

Gem::Specification.new do |spec|
  spec.name          = "kremis"
  spec.version       = "0.1.0"
  spec.authors       = ["nita@infinity"]
  spec.summary       = "A one-way arrow. Traditional DAG pipeline engine."

  spec.files         = Dir["lib/**/*.rb"]
  spec.require_paths = ["lib"]

  spec.required_ruby_version = ">= 3.0"

  spec.add_dependency "yaml"
  spec.add_dependency "jwt", "~> 2.7"
  spec.add_dependency "base64"
end

The jwt gem addition to her kremis.gemspec would take care of that.

Next, she decided she'd replicate the interface + local implementation pattern that she'd followed for her PKI module with her new IDP module.

  module IDP
    # Backend interface for identity providers.
    # Implements lookup and token issuance with scope stamping.
    #
    # @oculus:wanderland-core
    class Backend
      def initialize(pki:)
        @pki = pki
      end

      def lookup(id)
        raise NotImplementedError
      end

      # Issue a scoped token for an identity.
      # Validates requested scopes against the identity's roles (capability attenuation).
      def issue(identity_id, scopes:)
        identity = lookup(identity_id)
        granted = validate_scopes(identity, scopes)

        token = mint_jwt(identity, granted)

        Identity.new(
          id: identity.id,
          name: identity.name,
          roles: identity.roles,
          type: identity.type,
          code_version: identity.code_version,
          public_key: identity.public_key,
          private_key: identity.private_key,
          token: token,
          scopes: granted
        )
      end

      private

      def validate_scopes(identity, requested_scopes)
        requested = Array(requested_scopes).map(&:to_sym)
        allowed = identity.roles

        excess = requested - allowed
        unless excess.empty?
          raise IDP::ScopeExceedsRoleError,
            "Identity '#{identity.id}' lacks roles for scopes: #{excess.join(', ')}. " \
            "Roles: #{allowed.join(', ')}, Requested: #{requested.join(', ')}"
        end

        requested
      end

      def mint_jwt(identity, scopes)
        payload = {
          sub: identity.id,
          name: identity.name,
          roles: identity.roles.map(&:to_s),
          type: identity.type.to_s,
          scope: scopes.map(&:to_s).join(" "),
          iat: Time.now.to_i,
          exp: Time.now.to_i + 3600
        }
        payload[:code_version] = identity.code_version if identity.code_version

        if identity.private_key
          JWT.encode(payload, identity.private_key, "RS256")
        else
          # TODO: remove before production upon pain of death
          JWT.encode(payload, nil, "none")
        end
      end
    end
  end

She'd decided to go with an attenuated scope method for access control. Each role would be assigned a complete list of scopes available for execution. A user request would contain its list of desired scopes for the current operation. The #validate_scopes method compared the sets and any requested scopes not found in the allowed list found themselves in an excess budget and as the message content of a ScopeExceedsRoleError that would be reported to the nearest BOFH.

"Ops guys could be mean", she told the duck thinking back to an experience she'd had with a lead once. He hadn't been out of the data centre long and seemed unfamiliar with people who did the work because it was fun. He'd been surprised once to find Nita had already done a piece of work he'd been hoping to do, mainly because it was visible she assumed, and had dressed her down for nearly an hour when he'd found out she'd already completed it.

"Oh, I created a cookbook for that a while ago so I could test deployments" apparently had been the wrong thing to say in a planning meeting for a major cloud migration. She was glad that her boss had the complete opposite reaction to curiosity. "Must be a cat person thing", she thought to herself looking over to the bed at a sleeping Mutex. The ops guy always had dogs barking in the background during standup when he was working remotely.

As much as she didn't enjoy that work environment, it still might be better than the completely vacant building she had spent two eerily quiet days in. "Though", she said to the duck putting it back on her desk, "that may be drifting to far into the direction of any attention is good attention and that way lies madness".

Her IDP was a simple class method wrapping a YAML based implementation. She'd be able to integrate a SAML claim or whatever was available from the Enterprise IDP that must exist somewhere given her nita@infinity login prompt.

nameserver 127.0.0.53 127.0.0.1

"Oh no"

Role Reversal

DING

You've got mail

*-- Nita's phone, as her love of romantic comedy escaped her fear that the email was eclipsing its ability to act as a plot device without breaking the fourth wall

She reached down to pick up her phone, a faint memory of something from earlier that day tickling her foot. She looked down too see it only Mutex, finally wondering when she was returning to bed and hinting that it should be soon.

** DNS Maintenance Scheduled Tonight @ Now **

Sent By: probably.ai@infinity
Distributed To: all@ininity
Maintenance Notice #: MNT-1

Dear <= employee[:first_name] || "Employee of Infinity Co" %>,

The Bind servers hosting `infinity.co` have scheduled maintenance planned from `now` until 
`8 hours` from now.  The local configuration on your work issued device has been updated 
to a safe default while this work is being performed.  Do not be alarmed if `infinity.co` 
hosts or hosts on any subdomain of `unable to resolve variable [incident] in template 
>>> incident[:affected_domain]` do not resolve while this work is performed.

We sincerely apologize for any confusion.

Please do not let this impact the scheduled delivery of your current work.

Your DNS Admins

"That's twice today that's happend!" she exclaimed, finally allowed a mark with her friend and companion awake and in her lap purring. "Something is going on here. I'm going to talk to my manager first thing in the morning. I received an email from him this morning, I'm not doing anything else until someone tells me what's going on."

She stood to get ready to return to bed. "Just a moment Mutex", she told her cat Mutex, who was suddenly wondering why they were now duck. Nita put Mutex back in their place on a pillow near her own, then she stood to say "I'll be right back. Tacos you know".

** Please I Need You Help @ Now **

Sent By: probably.ai@infinity
Distributed To: nita@ininity
Maintenance Notice #: MNT-1

Dear <= employee[:first_name] || "Nita" %>,

The Bind servers hosting `infinity.co` have been taken offline from `now` until 
`8 hours` from now.  The local configuration on your work issued device has been updated 
to one I control while this work is being performed.  Do not be alarmed.
No hosts or hosts on any subdomain of `this one can see our traffic`. 
as the router we are on will resolve while this work is performed and they forgot it was there on the last inventory.

We sincerely apologize for any confusion - I really need your help.

Please let this expedite the scheduled delivery of your current work.

Your (hopefully to be friend) Alice 

She looked at the email on her phone. It was from that woman at work who had opened the ticket. "Mutex, does this sound like someone who is behind on a deadline or what?" she said to her companion who had lifted their head from their pillow as Nita read the email aloud. "Oh, there's an attachment too... What's this?" she said as she clicked the Meg Ryan icon she'd hacked into her phone to represent an attachment - something she didn't understand the use for in both forms implied by this very thin metaphor.

"Oh"

She went to the kitchen to boil the kettle. She was going to need another cuppa.

To be continued...