├── cli
├── .gitignore
├── .dockerignore
├── Dockerfile
├── lib
│ ├── is-docker.js
│ └── index.js
├── .eslintrc
├── package.json
├── .snyk
└── README.md
├── site
├── static
│ ├── CNAME
│ ├── favicon.ico
│ ├── logo
│ │ ├── logo.png
│ │ └── LICENSE
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── apple-touch-icon.png
│ ├── third_party
│ │ └── GitHub-Mark-120px-plus.png
│ ├── browserconfig.xml
│ └── site.webmanifest
├── .gitignore
├── data
│ └── apiVersions.yaml
├── content
│ └── docs
│ │ ├── _index.md
│ │ ├── images
│ │ └── diagram.png
│ │ ├── threatmodeling
│ │ ├── images
│ │ │ ├── diagram.png
│ │ │ ├── diagram-crd.png
│ │ │ ├── kamus-pod.png
│ │ │ ├── diagram.pu
│ │ │ └── diagram-crd.pu
│ │ ├── security.md
│ │ ├── controls
│ │ │ ├── KMS
│ │ │ │ ├── obfuscate_key_names.md
│ │ │ │ ├── use_hsm.md
│ │ │ │ ├── hardening_credentials.md
│ │ │ │ └── firewall_protection.md
│ │ │ ├── decryption
│ │ │ │ ├── k8s_api_tls.md
│ │ │ │ ├── deny_default_sa.md
│ │ │ │ ├── kamus_in_cluster_tls.md
│ │ │ │ ├── deny_secret_view.md
│ │ │ │ └── opa_pods_secrets.md
│ │ │ └── encryption
│ │ │ │ ├── certificate_pinning.md
│ │ │ │ ├── deny_default_sa.md
│ │ │ │ ├── client_side_encryption.md
│ │ │ │ ├── ip_throttling.md
│ │ │ │ └── block_internet_access.md
│ │ ├── threats
│ │ │ ├── kms
│ │ │ │ ├── quantom_computing.md
│ │ │ │ └── leaked_credentials.md
│ │ │ ├── encryption
│ │ │ │ ├── sniffing_user_traffic.md
│ │ │ │ ├── denial_of_service.md
│ │ │ │ └── namespace_enumeration.md
│ │ │ └── decryption
│ │ │ │ ├── sniffing_tampering.md
│ │ │ │ ├── leveraging_crd.md
│ │ │ │ └── pod_impersonation.md
│ │ ├── architecture.md
│ │ └── threats_controls.md
│ │ ├── contributing
│ │ └── roadmap.md
│ │ └── user
│ │ ├── known-issues.md
│ │ ├── quick-start.md
│ │ └── crd.md
├── Makefile
├── layouts
│ ├── robots.txt
│ ├── partials
│ │ ├── inlinecss.html
│ │ ├── inlinescript.html
│ │ ├── fancymarkdown.html
│ │ ├── navbar.html
│ │ ├── footer.html
│ │ ├── header.html
│ │ └── sidebar.html
│ ├── index.redirects
│ ├── index.html
│ └── docs
│ │ ├── index.html
│ │ ├── section.html
│ │ └── single.html
├── package.json
├── README.md
├── config.toml
└── assets
│ └── js
│ └── inline.js
├── init-container
├── tests
│ ├── token
│ ├── encrypted
│ │ ├── key1
│ │ ├── key2
│ │ ├── key.json
│ │ └── key-newlines.json
│ ├── expected-custom.txt
│ ├── templates
│ │ └── template.ejs
│ ├── expected.cfg
│ ├── expected-strict.cfg
│ ├── expected.json
│ ├── Wiremock
│ │ ├── Dockerfile
│ │ └── mappings
│ │ │ ├── api_v1_decrypt-c5291391-7520-4d8d-a39a-9466ceb759e4.json
│ │ │ ├── api_v2_decrypt-c5291391-7520-4d8d-a39a-9466ceb759e4.json
│ │ │ ├── api_v3_decrypt-c5291391-7520-4d8d-a39a-9466ceb759e4.json
│ │ │ └── api_v4_decrypt-c5291391-7520-4d8d-a39a-9466ceb759e4.json
│ ├── docker-compose.yaml
│ └── run_test.sh
├── .dockerignore
├── .gitignore
├── encrypted
│ └── key
├── templates
│ ├── cfg.ejs
│ ├── json.ejs
│ └── cfg-strict.ejs
├── Dockerfile
├── package.json
└── CONTRIBUTING.md
├── tests
├── crd-controller
│ ├── requirements.txt
│ ├── .vscode
│ │ └── settings.json
│ ├── service-account.yaml
│ ├── kind-config.yaml
│ ├── tls-Secret.yaml
│ ├── updated-tls-KamusSecretV1Alpha2.yaml
│ ├── key.crt
│ ├── crd.yaml
│ ├── crd-controller.csproj
│ ├── deployment.yaml
│ ├── tls-KamusSecretV1Alpha2.yaml
│ ├── tls-KamusSecretV1Alpha2-with-annotations.yaml
│ └── run-tests.sh
├── blackbox
│ ├── compose
│ │ ├── docker-compose.ci.yaml
│ │ ├── reports
│ │ │ └── glue.json
│ │ ├── glue
│ │ │ └── glue.json
│ │ ├── docker-compose.local.yaml
│ │ └── docker-compose.yaml
│ ├── appsettings.json
│ ├── Wiremock
│ │ ├── Dockerfile
│ │ └── mappings
│ │ │ ├── api_v1_namespaces_default_serviceaccounts_default-223d2c84-2e62-451a-a352-829107a55828.json
│ │ │ ├── apis_authorizationk8sio_v1_selfsubjectaccessreviews-b94a1942-1c74-40cd-9136-0df1d2217e77.json
│ │ │ └── apis_authenticationk8sio_v1_tokenreviews-ff46d3b4-0aad-422d-8215-e99cff8f3188.json
│ ├── Dockerfile
│ ├── Utils
│ │ ├── baerer
│ │ │ ├── JwtProvider.cs
│ │ │ └── JwtSignInHandler.cs
│ │ ├── ConfigurationProvider.cs
│ │ └── HttpClientProvider.cs
│ ├── MonitoringControllerTests.cs
│ ├── blackbox.csproj
│ └── run_test.sh
├── integration
│ ├── settings.json
│ ├── Utils.cs
│ ├── integration.csproj
│ ├── AwsKeyManagementTests.cs
│ ├── GoogleCloudKeyManagementTests.cs
│ └── EnvelopeDecoratorIntegrationTests.cs
└── unit
│ ├── unit.csproj
│ └── KeyManagment
│ └── SymmetricKeyManagmentTests.cs
├── example
├── app
│ ├── Dockerfile
│ ├── config.json
│ └── index.php
├── deployment-kamus
│ ├── service-account.yaml
│ ├── configmap.yaml
│ └── deployment.yaml
├── deployment-secret
│ ├── secret.yaml
│ └── deployment.yaml
└── README.md
├── images
└── logo.png
├── .dockerignore
├── package.json
├── security
└── smime.p7m
├── renovate.json
├── glue
└── glue.json
├── scripts
├── run_security_tests.sh
├── teardown_tests.sh
└── run_tests.sh
├── src
├── decrypt-api
│ ├── KubernetesAuthentication
│ │ ├── KubernetesAuthenticationOptions.cs
│ │ └── KubernetesAuthenticationHandler.cs
│ ├── Extensions
│ │ └── LoggingExtensions.cs
│ ├── Models
│ │ └── DecryptRequest.cs
│ ├── appsettings.json
│ ├── Properties
│ │ └── launchSettings.json
│ ├── appsettings.Development.json
│ ├── ErrorHandlingMiddleware.cs
│ ├── decrypt-api.csproj
│ ├── Program.cs
│ └── Controllers
│ │ ├── MonitoringController.cs
│ │ └── DecryptController.cs
├── encrypt-api
│ ├── Extensions
│ │ └── LoggingExtensions.cs
│ ├── appsettings.json
│ ├── Controllers
│ │ ├── MonitoringController.cs
│ │ └── EncryptController.cs
│ ├── Models
│ │ └── EncryptRequest.cs
│ ├── appsettings.Development.json
│ ├── Properties
│ │ └── launchSettings.json
│ ├── ErrorHandlingMiddleware.cs
│ ├── encrypt-api.csproj
│ ├── Program.cs
│ └── Startup.cs
├── crd-controller
│ ├── utils
│ │ ├── LoggingExtensions.cs
│ │ ├── KeyManagementExtensions.cs
│ │ └── KubernetesExtensions.cs
│ ├── metrics
│ │ └── Counters.cs
│ ├── appsettings.Development.json
│ ├── Controllers
│ │ └── MonitoringController.cs
│ ├── Models
│ │ └── V1Alpha2
│ │ │ └── KamusSecret.cs
│ ├── appsettings.json
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Program.cs
│ ├── LoggingMiddleware.cs
│ ├── crd-controller.csproj
│ └── HealthChecks
│ │ └── KubernetesPermissionsHelthCheck.cs
└── key-managment
│ ├── KeyIdCreator.cs
│ ├── IKeyManagement.cs
│ ├── key-managment.csproj
│ ├── EnvelopeEncryptionUtils.cs
│ ├── SymmetricKeyManagement.cs
│ ├── EnvelopeEncryptionDecorator.cs
│ ├── RijndaelUtils.cs
│ └── AzureKeyVaultKeyManagement.cs
├── security.md
├── ci
├── version_to_deploy_cli_docker.sh
├── version_to_deploy_init.sh
└── version_to_deploy.sh
├── .grenrc.json
├── .gitignore
├── Dockerfile
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── release_management.md
/cli/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/site/static/CNAME:
--------------------------------------------------------------------------------
1 | kamus.soluto.io
--------------------------------------------------------------------------------
/init-container/tests/token:
--------------------------------------------------------------------------------
1 | some-jwt
--------------------------------------------------------------------------------
/init-container/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules/**
--------------------------------------------------------------------------------
/init-container/tests/encrypted/key1:
--------------------------------------------------------------------------------
1 | value
--------------------------------------------------------------------------------
/init-container/tests/encrypted/key2:
--------------------------------------------------------------------------------
1 | dummy
--------------------------------------------------------------------------------
/site/.gitignore:
--------------------------------------------------------------------------------
1 | public/*
2 | resources/*
--------------------------------------------------------------------------------
/tests/crd-controller/requirements.txt:
--------------------------------------------------------------------------------
1 | kubetest
2 |
--------------------------------------------------------------------------------
/init-container/tests/encrypted/key.json:
--------------------------------------------------------------------------------
1 | {"key":"value"}
--------------------------------------------------------------------------------
/init-container/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | out.json
3 | out.cfg
--------------------------------------------------------------------------------
/example/app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:7.4-apache
2 | COPY . /var/www/html/
--------------------------------------------------------------------------------
/init-container/tests/expected-custom.txt:
--------------------------------------------------------------------------------
1 | super-secret-from-value
2 | hello
--------------------------------------------------------------------------------
/site/data/apiVersions.yaml:
--------------------------------------------------------------------------------
1 | - v1alpha1
2 | - v1alpha2
3 | - v1alpha3
4 |
--------------------------------------------------------------------------------
/example/app/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "key": "value",
3 | "key2": "value"
4 | }
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/images/logo.png
--------------------------------------------------------------------------------
/init-container/encrypted/key:
--------------------------------------------------------------------------------
1 | ezBR+Ew+Itwg6fA/tQjxzg==:/DH+kSV3UN8eRUxT/cJp5w==
--------------------------------------------------------------------------------
/init-container/tests/templates/template.ejs:
--------------------------------------------------------------------------------
1 | <%= secrets["key1"] %>
2 | hello
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | bin\
2 | obj\
3 | cmd/
4 | **/bin/*
5 | **/obj/*
6 | **/vendor
--------------------------------------------------------------------------------
/init-container/tests/encrypted/key-newlines.json:
--------------------------------------------------------------------------------
1 | {"key-newlines":"value\nvalue"}
--------------------------------------------------------------------------------
/site/content/docs/_index.md:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "url-join": "4.0.1"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/security/smime.p7m:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/security/smime.p7m
--------------------------------------------------------------------------------
/cli/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .dockerignore
3 | Dockerfile
4 | test
5 | npm-debug.log
6 | .git
--------------------------------------------------------------------------------
/site/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/site/static/favicon.ico
--------------------------------------------------------------------------------
/site/static/logo/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/site/static/logo/logo.png
--------------------------------------------------------------------------------
/tests/crd-controller/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "env/bin/python3.7"
3 | }
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "automerge": true
6 | }
7 |
--------------------------------------------------------------------------------
/site/Makefile:
--------------------------------------------------------------------------------
1 | serve:
2 | hugo server \
3 | --ignoreCache \
4 | --buildFuture
5 |
6 | build:
7 | hugo
--------------------------------------------------------------------------------
/site/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/site/static/favicon-16x16.png
--------------------------------------------------------------------------------
/site/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/site/static/favicon-32x32.png
--------------------------------------------------------------------------------
/site/static/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/site/static/apple-touch-icon.png
--------------------------------------------------------------------------------
/tests/blackbox/compose/docker-compose.ci.yaml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | api:
4 | image: $IMAGE_TAG
5 |
--------------------------------------------------------------------------------
/glue/glue.json:
--------------------------------------------------------------------------------
1 | {
2 | "50001_Resource Allows Anonymous Access_http://encryptor:9999/api/v1/encrypt_POST": "ignore"
3 | }
--------------------------------------------------------------------------------
/site/content/docs/images/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/site/content/docs/images/diagram.png
--------------------------------------------------------------------------------
/tests/crd-controller/service-account.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: crd-controller
--------------------------------------------------------------------------------
/example/deployment-kamus/service-account.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: kamus-example-sa
--------------------------------------------------------------------------------
/cli/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16.5.0-alpine
2 |
3 | WORKDIR /app
4 | COPY . .
5 | RUN yarn
6 |
7 | ENTRYPOINT ["node", "lib/index.js"]
8 |
--------------------------------------------------------------------------------
/tests/blackbox/compose/reports/glue.json:
--------------------------------------------------------------------------------
1 | {
2 | "50001_Resource Allows Anonymous Access_http://encryptor:9999/api/v1/encrypt_POST": "new"
3 | }
--------------------------------------------------------------------------------
/site/static/third_party/GitHub-Mark-120px-plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/site/static/third_party/GitHub-Mark-120px-plus.png
--------------------------------------------------------------------------------
/tests/crd-controller/kind-config.yaml:
--------------------------------------------------------------------------------
1 | kind: Cluster
2 | apiVersion: kind.x-k8s.io/v1alpha4
3 | nodes:
4 | - role: control-plane
5 | - role: worker
--------------------------------------------------------------------------------
/init-container/templates/cfg.ejs:
--------------------------------------------------------------------------------
1 | <%_ Object.keys(secrets).forEach(function(key){ -%>
2 | <%= key %>=<%- stringifyIfJson(secrets[key]) %>
3 | <%_ }); -%>
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/images/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/site/content/docs/threatmodeling/images/diagram.png
--------------------------------------------------------------------------------
/site/layouts/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | {{ if ne ( getenv "HUGO_ENV" ) "production" -}}
3 | Disallow: /
4 | {{- else -}}
5 | Allow: /
6 | {{- end -}}
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/images/diagram-crd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/site/content/docs/threatmodeling/images/diagram-crd.png
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/images/kamus-pod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Soluto/kamus/HEAD/site/content/docs/threatmodeling/images/kamus-pod.png
--------------------------------------------------------------------------------
/site/layouts/partials/inlinecss.html:
--------------------------------------------------------------------------------
1 | {{ with resources.Get "css/inline.css" | resources.Minify }}
2 |
3 | {{ end }}
--------------------------------------------------------------------------------
/site/layouts/partials/inlinescript.html:
--------------------------------------------------------------------------------
1 | {{ with resources.Get "js/inline.js" | resources.Minify }}
2 |
3 | {{ end }}
--------------------------------------------------------------------------------
/tests/blackbox/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "API_URL": "http://localhost:5000/",
3 | "PROXY_URL": "",
4 | "KUBERNETES_URL": "http://localhost:8080"
5 | }
6 |
--------------------------------------------------------------------------------
/tests/crd-controller/tls-Secret.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Secret
3 | metadata:
4 | name: my-tls-secret
5 | type: TlsSecret
6 | data:
7 | key: aGVsbG8=
--------------------------------------------------------------------------------
/site/static/logo/LICENSE:
--------------------------------------------------------------------------------
1 | # The Kamus logo files are licensed under a choice of either Apache-2.0 or CC-BY-4.0 (Creative Commons Attribution 4.0 International).
2 |
--------------------------------------------------------------------------------
/example/app/index.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | PHP Test
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/deployment-kamus/configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: encrypted-secrets-cm
5 | data:
6 | token: ezBR+Ew+Itwg6fA/tQjxzg==:/DH+kSV3UN8eRUxT/cJp5w==
--------------------------------------------------------------------------------
/example/deployment-secret/secret.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Secret
3 | metadata:
4 | name: kamus-example-secret
5 | type: Opaque
6 | data:
7 | config.json: ewogICAgImtleSI6ICJ0ZXN0Igp9
--------------------------------------------------------------------------------
/site/layouts/partials/fancymarkdown.html:
--------------------------------------------------------------------------------
1 | {{ . | replaceRE "()" `${1} 🔗︎ ${3}` | safeHTML }}
--------------------------------------------------------------------------------
/init-container/tests/expected.cfg:
--------------------------------------------------------------------------------
1 | key-newlines.json={"secret_key":"secret_value\nsecret_value"}
2 | key.json={"secret_key":"secret_value"}
3 | key1=super-secret-from-value
4 | key2=super-secret
5 |
--------------------------------------------------------------------------------
/scripts/run_security_tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | docker run --rm -v $(pwd)/glue:/input soluto/glue-ci:1532426297485 sh -x /app/run_glue.sh /input/glue.json /input/report.json
--------------------------------------------------------------------------------
/site/layouts/index.redirects:
--------------------------------------------------------------------------------
1 | {{- $apiVersions := site.Data.apiVersions -}}
2 | {{- range $apiVersions }}
3 | /{{ . }} https://godoc.org/sigs.k8s.io/kind/pkg/cluster/config/{{ . }}
4 | {{- end }}
5 |
--------------------------------------------------------------------------------
/init-container/tests/expected-strict.cfg:
--------------------------------------------------------------------------------
1 | key-newlines.json={"secret_key":"secret_value\nsecret_value"}
2 | key.json={"secret_key":"secret_value"}
3 | key1="super-secret-from-value"
4 | key2="super-secret"
5 |
--------------------------------------------------------------------------------
/init-container/tests/expected.json:
--------------------------------------------------------------------------------
1 | {
2 | "key-newlines.json":{"secret_key":"secret_value\nsecret_value"},
3 | "key.json":{"secret_key":"secret_value"},
4 | "key1":"super-secret-from-value",
5 | "key2":"super-secret"
6 | }
--------------------------------------------------------------------------------
/init-container/templates/json.ejs:
--------------------------------------------------------------------------------
1 | {
2 | <%_ Object.keys(secrets).forEach(function(key, index){ -%>
3 | "<%= key %>":<%- JSON.stringify(secrets[key]) -%> <%_ if(Object.keys(secrets).length - 1 > index) { -%>,<%_ } %>
4 | <%_ }); -%>
5 | }
--------------------------------------------------------------------------------
/tests/crd-controller/updated-tls-KamusSecretV1Alpha2.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: "soluto.com/v1alpha2"
2 | kind: KamusSecret
3 | metadata:
4 | name: my-tls-secret
5 | type: TlsSecret
6 | stringData:
7 | key: fX+zM9o709PGkitf0f7PNg==:1iLWChg0N5+SwysTXvLSCw==
8 | serviceAccount: some-sa
--------------------------------------------------------------------------------
/init-container/templates/cfg-strict.ejs:
--------------------------------------------------------------------------------
1 | <%_ Object.keys(secrets).forEach(function(key){ -%>
2 | <%_ if(typeof(secrets[key]) === "string") { -%>
3 | <%= key %>="<%- secrets[key] %>"
4 | <%_ } else { -%>
5 | <%= key %>=<%- stringifyIfJson(secrets[key]) %>
6 | <%_ } -%>
7 | <%_ }); -%>
--------------------------------------------------------------------------------
/tests/blackbox/compose/glue/glue.json:
--------------------------------------------------------------------------------
1 | {
2 | "ZAPhttp://api:9999/api/v1/isAliveResource Allows Anonymous Access": "ignore",
3 | "ZAPhttp://api:9999/api/v1/encryptResource Allows Anonymous Access": "ignore",
4 | "ZAPhttp://api:9999/api/v1/encryptBase64 Disclosure": "ignore"
5 | }
--------------------------------------------------------------------------------
/src/decrypt-api/KubernetesAuthentication/KubernetesAuthenticationOptions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authentication;
2 |
3 | namespace Kamus.KubernetesAuthentication
4 | {
5 | public class KubernetesAuthenticationOptions : AuthenticationSchemeOptions
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tests/blackbox/Wiremock/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:8-stretch
2 |
3 | WORKDIR /wiremock
4 |
5 | RUN wget https://repo1.maven.org/maven2/com/github/tomakehurst/wiremock-standalone/2.17.0/wiremock-standalone-2.17.0.jar
6 |
7 | COPY . .
8 |
9 | CMD ["java", "-jar", "wiremock-standalone-2.17.0.jar"]
--------------------------------------------------------------------------------
/tests/integration/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "KeyManagement": {
3 | "Provider": "AESKey",
4 | "AES": {
5 | "Key": "rWnWbaFutavdoeqUiVYMNJGvmjQh31qaIej/vAxJ9G0="
6 | },
7 | "KeyVault": {
8 | "KeyType": "RSA",
9 | "KeyLength": "2048"
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/init-container/tests/Wiremock/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:8-stretch
2 |
3 | WORKDIR /wiremock
4 |
5 | RUN wget https://repo1.maven.org/maven2/com/github/tomakehurst/wiremock-standalone/2.17.0/wiremock-standalone-2.17.0.jar
6 |
7 | COPY . .
8 |
9 | CMD ["java", "-jar", "wiremock-standalone-2.17.0.jar"]
--------------------------------------------------------------------------------
/site/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kamus-website",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "dependencies": {
7 | "hugo-bin": "^0.62.0"
8 | },
9 | "scripts": {
10 | "build": "hugo",
11 | "serve": "hugo server -w"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/site/static/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #2d89ef
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/decrypt-api/Extensions/LoggingExtensions.cs:
--------------------------------------------------------------------------------
1 | using Serilog;
2 |
3 | namespace Kamus.Extensions
4 | {
5 | public static class LoggingExtensions
6 | {
7 | public static ILogger AsAudit(this ILogger logger)
8 | {
9 | return logger.ForContext("log_type", "audit");
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/encrypt-api/Extensions/LoggingExtensions.cs:
--------------------------------------------------------------------------------
1 | using Serilog;
2 |
3 | namespace Kamus.Extensions
4 | {
5 | public static class LoggingExtensions
6 | {
7 | public static ILogger AsAudit(this ILogger logger)
8 | {
9 | return logger.ForContext("log_type", "audit");
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/security.md:
--------------------------------------------------------------------------------
1 | # Reporting Security Issues
2 |
3 | If you discover a security issue in Kamus, please report it by sending an email to security@soluto.com.
4 |
5 | This will allow us to assess the risk, and make a fix available before we add a bug report to the GitHub repository.
6 |
7 | Thanks for helping make Kamus safe for everyone.
8 |
9 |
--------------------------------------------------------------------------------
/tests/blackbox/compose/docker-compose.local.yaml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | encryptor:
4 | build:
5 | context: ../../../.
6 | args:
7 | PROJECT_NAME: encrypt-api
8 | decryptor:
9 | build:
10 | context: ../../../.
11 | args:
12 | PROJECT_NAME: decrypt-api
13 |
--------------------------------------------------------------------------------
/site/layouts/partials/navbar.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/decrypt-api/Models/DecryptRequest.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace Kamus.Models
4 | {
5 | public class DecryptRequest
6 | {
7 | [JsonProperty(PropertyName = "data", Required = Required.Always)]
8 | public string EncryptedData
9 | {
10 | get;
11 | set;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/scripts/teardown_tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | api_file="docker-compose.ci.yaml"
6 |
7 | if [[ -z $IMAGE_TAG ]];
8 | then
9 | api_file="docker-compose.local.yaml"
10 | fi
11 |
12 | docker-compose -f tests/blackbox/compose/docker-compose.yaml -f tests/blackbox/compose/$api_file -f tests/blackbox/compose/docker-compose.security.yaml down
--------------------------------------------------------------------------------
/src/crd-controller/utils/LoggingExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Serilog;
3 |
4 | namespace CustomResourceDescriptorController.Extensions
5 | {
6 | public static class LoggingExtensions
7 | {
8 | public static ILogger AsAudit(this ILogger logger)
9 | {
10 | return logger.ForContext("log_type", "audit");
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/crd-controller/metrics/Counters.cs:
--------------------------------------------------------------------------------
1 | using App.Metrics;
2 | using App.Metrics.Counter;
3 |
4 | namespace CustomResourceDescriptorController.metrics
5 | {
6 | public static class Counters
7 | {
8 | public static CounterOptions EventReceived = new CounterOptions
9 | {
10 | Name = "Event Received",
11 | MeasurementUnit = Unit.Calls,
12 | };
13 |
14 | }
15 | }
--------------------------------------------------------------------------------
/site/layouts/partials/footer.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/init-container/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:10-alpine
2 |
3 | RUN mkdir /home/node/app
4 | # Create app directory
5 | WORKDIR /home/node/app
6 |
7 | # Install app dependencies
8 | # A wildcard is used to ensure both package.json AND package-lock.json are copied
9 | # where available (npm@5+)
10 | COPY package*.json yarn.lock ./
11 |
12 | RUN yarn --prod
13 |
14 | # Bundle app source
15 | COPY . .
16 |
17 | USER node
18 |
19 | ENTRYPOINT [ "node", "index.js" ]
--------------------------------------------------------------------------------
/site/layouts/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ partial "header.html" . }}
4 |
5 | {{ partial "sidebar.html" . }}
6 | {{ partial "navbar.html" . }}
7 |
8 |
{{ partial "fancymarkdown.html" .Content }}
9 | {{ partial "footer.html" . }}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/site/layouts/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ partial "header.html" . }}
4 |
5 | {{ partial "sidebar.html" . }}
6 | {{ partial "navbar.html" . }}
7 |
8 |
{{ partial "fancymarkdown.html" .Content }}
9 | {{ partial "footer.html" . }}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/site/layouts/docs/section.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ partial "header.html" . }}
4 |
5 | {{ partial "sidebar.html" . }}
6 | {{ partial "navbar.html" . }}
7 |
8 |
{{ partial "fancymarkdown.html" .Content }}
9 | {{ partial "footer.html" . }}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/site/layouts/docs/single.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ partial "header.html" . }}
4 |
5 | {{ partial "sidebar.html" . }}
6 | {{ partial "navbar.html" . }}
7 |
8 |
{{ partial "fancymarkdown.html" .Content }}
9 | {{ partial "footer.html" . }}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/site/README.md:
--------------------------------------------------------------------------------
1 | # Kamus's docs
2 |
3 | Januys's docs are built with [hugo], and can be browsed live at https://kamus.soluto.io/
4 |
5 | To browse them locally, install hugo and run `make serve` from this directory.
6 |
7 | ## Attribution
8 | Kamus docs are based on [kind] docs. All the content was replaced, but the underline framework is the same. Thank you Kind team for building your website!
9 |
10 | [hugo]: https://gohugo.io
11 | [kind]: https://kind.sigs.k8s.io
12 |
--------------------------------------------------------------------------------
/ci/version_to_deploy_cli_docker.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo checking init container version
4 | CLI_VERSION=$(grep -E "\"version\"" ./cli/package.json | grep -Eo "[0-9.]*(-rc[0-9]*)?")
5 | CLI_TAG="cli-docker-$CLI_VERSION"
6 | export CLI_DOCKER_TAG="latest"
7 | if [[ "$(git tag | grep -c "$CLI_TAG")" == "0" ]]; then
8 | echo tagging "$CLI_TAG"
9 | git tag "$CLI_TAG"
10 | export CLI_DOCKER_TAG=$CLI_VERSION
11 | fi
12 |
13 | echo "export CLI_DOCKER_TAG=$CLI_DOCKER_TAG" >> "$BASH_ENV"
--------------------------------------------------------------------------------
/src/crd-controller/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.Console" ],
4 | "MinimumLevel": "Debug",
5 | "WriteTo": [
6 | {
7 | "Name": "Console",
8 | }
9 | ],
10 | "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
11 | },
12 | "KeyManagement": {
13 | "Provider": "AESKey",
14 | "AES": {
15 | "Key": "rWnWbaFutavdoeqUiVYMNJGvmjQh31qaIej/vAxJ9G0="
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/decrypt-api/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.Console" ],
4 | "MinimumLevel": "Information",
5 | "WriteTo": [
6 | {
7 | "Name": "Console",
8 | "Args": {
9 | "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog"
10 | }
11 | }
12 | ],
13 | "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
14 | },
15 | "KeyManagement": {
16 | "Provider": "AzureKeyVault"
17 | }
18 | }
--------------------------------------------------------------------------------
/src/encrypt-api/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.Console" ],
4 | "MinimumLevel": "Information",
5 | "WriteTo": [
6 | {
7 | "Name": "Console",
8 | "Args": {
9 | "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog"
10 | }
11 | }
12 | ],
13 | "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
14 | },
15 | "KeyManagement": {
16 | "Provider": "AzureKeyVault"
17 | }
18 | }
--------------------------------------------------------------------------------
/tests/blackbox/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/core/sdk:3.0 as source
2 |
3 | WORKDIR /app
4 |
5 | RUN apt-get update \
6 | && apt-get install -y jq \
7 | && rm -rf /var/lib/apt/lists/*
8 | RUN wget https://raw.githubusercontent.com/vishnubob/wait-for-it/8ed92e8cab83cfed76ff012ed4a36cef74b28096/wait-for-it.sh
9 | RUN chmod +x wait-for-it.sh
10 |
11 | COPY blackbox.csproj .
12 | RUN dotnet restore blackbox.csproj
13 | COPY . .
14 | RUN dotnet build -c Release
15 |
16 | ENTRYPOINT ["./run_test.sh"]
--------------------------------------------------------------------------------
/site/static/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/security.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Reporting Security Issues"
3 | menu:
4 | main:
5 | parent: "Threat Modeling"
6 | identifier: "security"
7 | weight: 3
8 | ---
9 |
10 | # Reporting Security Issues
11 |
12 | If you discover a security issue in Kamus, please report it by sending an email to security@soluto.com.
13 |
14 | This will allow us to assess the risk, and make a fix available before we add a bug report to the GitHub repository.
15 |
16 | Thanks for helping make Kamus safe for everyone.
17 |
18 |
--------------------------------------------------------------------------------
/src/key-managment/KeyIdCreator.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Cryptography;
2 | using System.Text;
3 | using Microsoft.AspNetCore.WebUtilities;
4 |
5 | namespace Kamus.KeyManagement
6 | {
7 | public static class KeyIdCreator
8 | {
9 | public static string Create(string s)
10 | {
11 | return
12 | WebEncoders.Base64UrlEncode(
13 | SHA256.Create().ComputeHash(
14 | Encoding.UTF8.GetBytes(s)))
15 | .Replace("_", "-");
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/crd-controller/Controllers/MonitoringController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 | using k8s;
5 | using k8s.Models;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.Extensions.Caching.Memory;
8 | using Serilog;
9 |
10 | namespace CustomResourceDescriptorController.Controllers
11 | {
12 | public class MonitoringController : Controller
13 | {
14 | [HttpGet]
15 | [Route("")]
16 | public string Welcome()
17 | {
18 | return "welcome";
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/scripts/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | api_file="docker-compose.ci.yaml"
6 |
7 | if [[ -z $IMAGE_TAG ]];
8 | then
9 | api_file="docker-compose.local.yaml"
10 | fi
11 |
12 | docker-compose -f tests/blackbox/compose/docker-compose.yaml -f tests/blackbox/compose/$api_file -f tests/blackbox/compose/docker-compose.security.yaml pull --parallel
13 | docker-compose -f tests/blackbox/compose/docker-compose.yaml -f tests/blackbox/compose/$api_file -f tests/blackbox/compose/docker-compose.security.yaml up --build --exit-code-from black-box --abort-on-container-exit
--------------------------------------------------------------------------------
/tests/blackbox/Utils/baerer/JwtProvider.cs:
--------------------------------------------------------------------------------
1 | namespace blackbox.utils.baerer {
2 | public static class JwtProvider {
3 | public static string Provide(string scope) {
4 | var handler = new JwtSignInHandler();
5 | var principal = new System.Security.Claims.ClaimsPrincipal (new [] {
6 | new System.Security.Claims.ClaimsIdentity (new [] {
7 | new System.Security.Claims.Claim ("scope", scope)
8 | })
9 | });
10 | return handler.BuildJwt(principal);
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/tests/unit/unit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.0
4 | false
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/init-container/tests/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 |
3 | services:
4 | decryptor:
5 | user: root
6 | image: $INIT_CONTAINER_IMAGE
7 | environment:
8 | - KAMUS_URL=http://wiremock:8080/
9 | volumes:
10 | - "${PWD}/output:/files:rw"
11 | - "${PWD}:/var/run/secrets/kubernetes.io/serviceaccount"
12 | - "${PWD}/encrypted:/encrypted"
13 |
14 | command: ["node","index.js", "-e", "/encrypted", "-d", "/files/", "-n", "out.${OUTPUT_FORMAT}", "-f", "${OUTPUT_FORMAT}"]
15 |
16 | wiremock:
17 | build:
18 | context: Wiremock
19 | ports:
20 | - 8080:8080
--------------------------------------------------------------------------------
/tests/blackbox/Utils/ConfigurationProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 |
3 | namespace blackbox.utils
4 | {
5 | public static class ConfigurationProvider
6 | {
7 | public static IConfiguration Configuration
8 | {
9 | get;
10 | set;
11 | }
12 |
13 | static ConfigurationProvider()
14 | {
15 | Configuration = new ConfigurationBuilder()
16 | .AddJsonFile("appsettings.json")
17 | .AddEnvironmentVariables()
18 | .Build();
19 | }
20 |
21 | }
22 | }
--------------------------------------------------------------------------------
/ci/version_to_deploy_init.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo checking init container version
4 | INIT_CONTAINER_VERSION=$(grep -E "\"version\"" ./init-container/package.json | grep -Eo "[0-9.]*(-rc[0-9]*)?")
5 | INIT_CONTAINER_TAG="init-container-$INIT_CONTAINER_VERSION"
6 | export INIT_CONTAINER_DOCKER_TAG="latest"
7 | if [[ "$(git tag | grep -c "$INIT_CONTAINER_TAG")" == "0" ]]; then
8 | echo tagging "$INIT_CONTAINER_TAG"
9 | git tag "$INIT_CONTAINER_TAG"
10 | export INIT_CONTAINER_DOCKER_TAG=$INIT_CONTAINER_VERSION
11 | fi
12 |
13 | echo "export INIT_CONTAINER_DOCKER_TAG=$INIT_CONTAINER_DOCKER_TAG" >> "$BASH_ENV"
--------------------------------------------------------------------------------
/src/crd-controller/Models/V1Alpha2/KamusSecret.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using k8s;
3 | using k8s.Models;
4 |
5 | namespace CustomResourceDescriptorController.Models.V1Alpha2
6 | {
7 | public class KamusSecret : KubernetesObject
8 | {
9 | public Dictionary Data { get; set; }
10 | public Dictionary StringData { get; set; }
11 | public string Type { get; set; }
12 | public V1ObjectMeta Metadata { get; set; }
13 | public string ServiceAccount { get; set; }
14 | public bool PropagateAnnotations { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/key-managment/IKeyManagement.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace Kamus.KeyManagement
5 | {
6 | public interface IKeyManagement
7 | {
8 | Task Encrypt(string data, string serviceAccountId, bool createKeyIfMissing = true);
9 | Task Decrypt(string encryptedData, string serviceAccountId);
10 | }
11 |
12 | public class DecryptionFailureException : Exception
13 | {
14 | public DecryptionFailureException(string reason, Exception innerException) : base(reason, innerException)
15 | {
16 |
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/cli/lib/is-docker.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | let isDocker;
4 |
5 | function hasDockerEnv() {
6 | try {
7 | fs.statSync('/.dockerenv');
8 | return true;
9 | } catch (err) {
10 | return false;
11 | }
12 | }
13 |
14 | function hasDockerCGroup() {
15 | try {
16 | return fs.readFileSync('/proc/self/cgroup', 'utf8').indexOf('docker') !== -1;
17 | } catch (err) {
18 | return false;
19 | }
20 | }
21 |
22 | function check() {
23 | return hasDockerEnv() || hasDockerCGroup();
24 | }
25 |
26 | module.exports = function () {
27 | if (isDocker === undefined) {
28 | isDocker = check();
29 | }
30 |
31 | return isDocker;
32 | };
--------------------------------------------------------------------------------
/.grenrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "dataSource": "issues",
3 | "prefix": "",
4 | "ignoreIssuesWith": ["duplicate", "internal", "invalid", "question"],
5 | "onlyMilestones": false,
6 | "ignoreTagsWith": ["init-container", "cli", "encryptor", "decryptor"],
7 | "groupBy": {
8 | "Enhancements:": ["enhancement", "documentation"],
9 | "Bug Fixes:": ["bug"]
10 | },
11 | "changelogFilename": "site/content/docs/user/CHANGELOG.md",
12 | "template": {
13 | "changelogTitle": "---\ntitle: \"Changelog\"\nmenu:\n main:\n parent: \"user\"\n identifier: \"changelog\"\n weight: 6\n---\n# Changelog\n\n"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/encrypt-api/Controllers/MonitoringController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Microsoft.AspNetCore.Authorization;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Mvc;
7 |
8 | namespace Kamus.Controllers
9 | {
10 | public class MonitoringController
11 | {
12 | [HttpGet]
13 | [Route("api/v1/isAlive")]
14 | public bool IsAlive()
15 | {
16 | return true;
17 | }
18 |
19 | [HttpGet]
20 | [Route("")]
21 | public string Welcome()
22 | {
23 | return "welcome";
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/KMS/obfuscate_key_names.md:
--------------------------------------------------------------------------------
1 | # Key names obfuscation
2 |
3 | Feature: Key names obfuscation
4 | In order to protect encryption keys
5 | As an engineer
6 | I want to obfuscate the key names
7 |
8 | Scenario: Using SHA256
9 | Given a namespace and service account
10 | When Kamus access KMS to retrieve the encryption key
11 | Then an obfuscated name will be created from the namespace and service account using SHA256
12 |
13 |
14 | ## Remarks
15 |
16 | * Mitigates:
17 | * Status: Partial implementation. In future version, we'll add support to add a salt.
18 |
19 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/decryption/k8s_api_tls.md:
--------------------------------------------------------------------------------
1 | # Use TLS when accessing Kuberentes API
2 |
3 | Feature: Use TLS when accessing Kuberentes API
4 | In order to protect Kamus from spoffing
5 | As an engineer
6 | I want to use TLS for all requests to Kubernetes API
7 |
8 | Scenario: Using token review API
9 | Given Kamus request to token review API
10 | And The request is not using TLS
11 | When sending the requesrt
12 | Then the request will fail
13 |
14 |
15 | ## Remarks
16 |
17 | * Mitigates:
18 | * [Impersonating pod to decrypt it's secrets](/docs/threatmodeling/threats/decryption/pod_impersonation)
19 |
20 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
21 |
--------------------------------------------------------------------------------
/example/deployment-secret/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | name: kamus-example
5 | labels:
6 | app: kamus-example
7 | spec:
8 | template:
9 | metadata:
10 | labels:
11 | app: kamus-example
12 | spec:
13 | serviceAccountName: kamus-example-sa
14 | automountServiceAccountToken: true
15 | containers:
16 | - name: app
17 | image: local/kamus:example
18 | imagePullPolicy: IfNotPresent
19 | volumeMounts:
20 | - name: secret-volume
21 | mountPath: /secrets
22 | volumes:
23 | - name: secret-volume
24 | secret:
25 | secretName: kamus-example-secret
26 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/KMS/use_hsm.md:
--------------------------------------------------------------------------------
1 | # Use KMS with HSM support
2 |
3 | Feature: Use KMS with HSM support
4 | In order to protect encryption keys
5 | As an engineer
6 | I want to store them using HSM
7 |
8 | Scenario: Azure KeyVauls
9 | Given Azure KeyVault keys created in HSM mode
10 | When a hacker tries to extract the private key
11 | Then the request will be denied
12 |
13 | ## Remarks
14 |
15 | * Mitigates:
16 | * [Accessing KMS with leaked credentials](/docs/threatmodeling/threats/kms/leaked_credentials)
17 | * References:
18 | * https://docs.microsoft.com/en-us/azure/key-vault/key-vault-hsm-protected-keys
19 |
20 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
--------------------------------------------------------------------------------
/src/crd-controller/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.Console" ],
4 | "MinimumLevel": {
5 | "Default": "Information",
6 | "Override": {
7 | "Microsoft": "Warning",
8 | "System": "Warning"
9 | }
10 | },
11 | "WriteTo": [
12 | {
13 | "Name": "Console",
14 | "Args": {
15 | "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog"
16 | }
17 | }
18 | ],
19 | "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
20 | },
21 | "KeyManagement": {
22 | "Provider": "AESKey",
23 | "AES": {
24 | "Key": "rWnWbaFutavdoeqUiVYMNJGvmjQh31qaIej/vAxJ9G0="
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/encrypt-api/Models/EncryptRequest.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace Kamus.Models
4 | {
5 | public class EncryptRequest
6 | {
7 | [JsonProperty(PropertyName = "service-account", Required = Required.Always)]
8 | public string ServiceAccountName
9 | {
10 | get;
11 | set;
12 | }
13 |
14 | [JsonProperty(PropertyName = "namespace", Required = Required.Always)]
15 | public string NamespaceName
16 | {
17 | get;
18 | set;
19 | }
20 |
21 | [JsonProperty(PropertyName = "data", Required = Required.Always)]
22 | public string Data
23 | {
24 | get;
25 | set;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/encrypt-api/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.Console" ],
4 | "MinimumLevel": "Information",
5 | "WriteTo": [
6 | {
7 | "Name": "Console",
8 | "Args": {
9 | "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog"
10 | }
11 | }
12 | ],
13 | "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
14 | },
15 | "KeyManagement": {
16 | "Provider": "AESKey",
17 | "AES": {
18 | "Key": "rWnWbaFutavdoeqUiVYMNJGvmjQh31qaIej/vAxJ9G0="
19 | },
20 | "KeyVault": {
21 | "KeyType": "RSA",
22 | "KeyLength": "2048",
23 | "MaximumDataLength": "214"
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/init-container/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "init-decryptor",
3 | "version": "1.4.3",
4 | "description": "Meant to be used inside init container to read encrypted values from a given folder and decrypt to them into a json in a given folder",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "docker build . -t init-container && export set INIT_CONTAINER_IMAGE=init-container",
8 | "test": "cd tests && ./run_test.sh",
9 | "pretest": "ejslint templates/*",
10 | "ejslint": "ejslint templates/*"
11 | },
12 | "author": "Soluto",
13 | "license": "MIT",
14 | "dependencies": {
15 | "axios": "0.21.3",
16 | "commander": "^5.0.0",
17 | "ejs": "^3.0.0",
18 | "ejs-lint": "^1.2.1",
19 | "node-readfiles": "^0.2.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/decrypt-api/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:49615/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "api": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "environmentVariables": {
22 | "ASPNETCORE_ENVIRONMENT": "Development"
23 | },
24 | "applicationUrl": "http://localhost:49621/"
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/encrypt-api/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:49615/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "api": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "environmentVariables": {
22 | "ASPNETCORE_ENVIRONMENT": "Development"
23 | },
24 | "applicationUrl": "http://localhost:49621/"
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/crd-controller/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:49615/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "crd_controller_v2": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "environmentVariables": {
22 | "ASPNETCORE_ENVIRONMENT": "Development"
23 | },
24 | "applicationUrl": "http://localhost:49621/"
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/KMS/hardening_credentials.md:
--------------------------------------------------------------------------------
1 | # Credentials Hardening
2 |
3 | Feature: Credentials Hardening
4 | In order to protect KMS
5 | As an engineer
6 | I want to harden the credentials used by Kamus
7 |
8 | Scenario: Short-lived credentials
9 | When I create KMS credentials
10 | Then I will use as short as possible expiration time
11 |
12 | Scenario: Use machine identity for authentication
13 | Given A machine idetity support is available
14 | When Kamus authenticate using this identity
15 | Then The request succeed
16 |
17 | ## Remarks
18 |
19 | * Mitigates:
20 | * [Accessing KMS with leaked credentials](/docs/threatmodeling/threats/kms/leaked_credentials)
21 | * References:
22 |
23 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
24 |
--------------------------------------------------------------------------------
/tests/integration/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Microsoft.IdentityModel.Clients.ActiveDirectory;
4 |
5 | namespace integration
6 | {
7 | public static class Utils
8 | {
9 | public static async Task AuthenticationCallback(string clientId, string clientSecret, string authority, string resource, string scope)
10 | {
11 | var authContext = new AuthenticationContext(authority);
12 | var clientCred = new ClientCredential(clientId, clientSecret);
13 | var result = await authContext.AcquireTokenAsync(resource, clientCred);
14 |
15 | if (result == null)
16 | throw new InvalidOperationException("Failed to obtain the JWT token");
17 |
18 | return result.AccessToken;
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/decryption/deny_default_sa.md:
--------------------------------------------------------------------------------
1 | # Deny request for default SA
2 |
3 | Feature: Deny request for default SA
4 | In order to protect pods from impersonation
5 | As an engineer
6 | I want to deny all requests with default service accounts
7 |
8 | Scenario: A decrypt request with token for default SA
9 | Given a pod that is mounted with default service account
10 | When the pod try to decrypt secrets using the token
11 | Then the operation denied
12 |
13 | ## Remarks
14 |
15 | * Mitigates:
16 | * [Impersonating pod to decrypt it's secrets](/docs/threatmodeling/threats/decryption/pod_impersonation)
17 | * References:
18 | * https://kubernetes.io/docs/reference/access-authn-authz/rbac/
19 |
20 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *.*~
3 | project.lock.json
4 | .DS_Store
5 | *.pyc
6 | nupkg/
7 |
8 | # Visual Studio Code
9 | .vscode/*
10 |
11 | # User-specific files
12 | *.suo
13 | *.user
14 | *.userosscache
15 | *.sln.docstates
16 |
17 | # Build results
18 | [Dd]ebug/
19 | [Dd]ebugPublic/
20 | [Rr]elease/
21 | [Rr]eleases/
22 | x64/
23 | x86/
24 | build/
25 | bld/
26 | [Bb]in/
27 | [Oo]bj/
28 | msbuild.log
29 | msbuild.err
30 | msbuild.wrn
31 |
32 | # Visual Studio 2015
33 | .vs/
34 |
35 | # Security tests
36 | zap/
37 | .idea/
38 | job/
39 |
40 | **/report.json
41 | **/report.json
42 | node_modules/
43 | **/vendor/
44 | init-container/token.txt
45 | init-container/decrypted/**
46 |
47 | kubectl
48 | kind
49 |
50 | docker-cache-api
51 | src/crd-controller/*.key
52 | src/crd-controller/*.crt
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/images/diagram.pu:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | actor User
4 | participant EncryptApi
5 | participant KMS
6 | participant DecryptApi
7 | participant Pod
8 | participant KubernetesApi
9 |
10 | == Encryption ==
11 |
12 | autonumber
13 | User -> EncryptApi: Encrypt - data, namespace, service account
14 | EncryptApi -> KMS: Encrypt - data, namespace, service account
15 | KMS -> EncryptApi: Enrypted data
16 | EncryptApi -> User: Enrypted data
17 |
18 | == Decryption ==
19 | autonumber 1
20 | Pod -> DecryptApi: Decrypt - data, service account token
21 | DecryptApi -> KubernetesApi: TokenReview - token
22 | KubernetesApi -> DecryptApi: service account, namespace
23 | DecryptApi -> KMS: Decrypt - data, service account, namespace
24 | KMS -> DecryptApi: Decrypted data
25 | DecryptApi -> Pod: Decrypted data
26 |
27 | @enduml
--------------------------------------------------------------------------------
/src/decrypt-api/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.Console" ],
4 | "MinimumLevel": "Information",
5 | "WriteTo": [
6 | {
7 | "Name": "Console",
8 | "Args": {
9 | "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog"
10 | }
11 | }
12 | ],
13 | "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
14 | },
15 | "Kubernetes": {
16 | "ProxyUrl": "http://localhost:8080"
17 | },
18 | "KeyManagement": {
19 | "Provider": "AESKey",
20 | "AES": {
21 | "Key": "rWnWbaFutavdoeqUiVYMNJGvmjQh31qaIej/vAxJ9G0="
22 | },
23 | "KeyVault": {
24 | "KeyType": "RSA",
25 | "KeyLength": "2048",
26 | "MaximumDataLength": "214"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/encryption/certificate_pinning.md:
--------------------------------------------------------------------------------
1 | # Certificate pinning
2 |
3 | Feature: Certificate pinning
4 | In order to protect Kamus from DoS
5 | As an engineer
6 | I want to protect the user from MitM attack
7 |
8 |
9 | Scenario: Certificate pinning
10 | Given a user that encrypt a secret using Kamus CLI
11 | When the user initiate a TLS session with Kamus
12 | Then the server certificate is validated with a pre-defined certificate
13 |
14 | ## Remarks
15 |
16 | * Mitigates:
17 | * [Sniffing user's traffic](/docs/threatmodeling/threats/encryption/sniffing_user_traffic)
18 | * References:
19 | * https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning
20 | * TODO: link to cert pinning docs in the CLI
21 |
22 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/encryption/deny_default_sa.md:
--------------------------------------------------------------------------------
1 | # Deny request for default SA
2 |
3 | Feature: Deny request for default SA
4 | In order to protect enumeration and make it harder to perform DoS attack
5 | As an engineer
6 | I want to deny all requests with default service account
7 |
8 | Scenario: An encrypt request for default SA
9 | Given a default service acount
10 | When the user try to encrypt data for this service account
11 | Then the operation denied
12 |
13 | ## Remarks
14 |
15 | * Mitigates:
16 | * [Kamus server disrupting](/docs/threatmodeling/threats/encryption/denial_of_service)
17 | * [Exposing names of namespaces and service accounts](/docs/threatmodeling/threats/encryption/namespace_enumeration)
18 |
19 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
20 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/decryption/kamus_in_cluster_tls.md:
--------------------------------------------------------------------------------
1 | # Serve Kamus API over TLS
2 |
3 | Feature: Serve Kamus API over TLS
4 | In order to protect Kamus from spoffing
5 | As an engineer
6 | I want to use TLS to encrypt traffic to and from Kamus
7 |
8 | Scenario: Using Kubernetes CA
9 | Given A certifcate from Kubernetes CA
10 | When Kamus serve a request
11 | Then The request will be encrypted using TLS
12 |
13 |
14 | ## Remarks
15 |
16 | * Mitigates:
17 | * [Sniff requests and responses to Kamus](/docs/threatmodeling/threats/decryption/sniffing_tampering)
18 | * References:
19 | * https://stackoverflow.com/questions/50893535/securing-kubernetes-service-with-tls
20 | * https://istio.io/docs/tasks/security/https-overlay/
21 |
22 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
23 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/encryption/client_side_encryption.md:
--------------------------------------------------------------------------------
1 | # Client-side encryption
2 |
3 | Feature: Client-side encryption
4 | In order to protect Kamus from DoS
5 | As an engineer
6 | I want to move all encryption logic to the client
7 |
8 | Scenario: Expose public key
9 | Given a user has aceess to the public key
10 | When the user need to encrypt a secret
11 | Then the user can encrypt it with the public key
12 | ## Remarks
13 |
14 | * Mitigates:
15 | * [Kamus server disrupting](/docs/threatmodeling/threats/encryption/denial_of_service)
16 | * [Sniffing user's traffic](/docs/threatmodeling/threats/encryption/sniffing_user_traffic)
17 | * Status: proposed. Currently we decided it's better to perform all encryption in the server side for simplicity.
18 |
19 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
--------------------------------------------------------------------------------
/init-container/tests/Wiremock/mappings/api_v1_decrypt-c5291391-7520-4d8d-a39a-9466ceb759e4.json:
--------------------------------------------------------------------------------
1 | {
2 | "id" : "c5291391-7520-4d8d-a39a-9466ceb759e4",
3 | "name" : "api_v1_decrypt",
4 | "request" : {
5 | "url" : "/api/v1/decrypt",
6 | "method" : "POST",
7 | "headers": {
8 | "Authorization": {
9 | "equalTo": "Bearer some-jwt"
10 | }
11 | },
12 | "bodyPatterns" : [ {
13 | "equalToJson" : "{\"data\":\"dummy\"}\n",
14 | "ignoreArrayOrder" : true,
15 | "ignoreExtraElements" : true
16 | } ]
17 | },
18 | "response" : {
19 | "status" : 200,
20 | "body" : "super-secret",
21 | "headers" : {
22 | "Content-Type" : "application/json",
23 | "Date" : "Thu, 12 Jul 2018 05:20:33 GMT"
24 | }
25 | },
26 | "uuid" : "c5291391-7520-4d8d-a39a-9466ceb759e4",
27 | "persistent" : true,
28 | "insertionIndex" : 2
29 | }
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/KMS/firewall_protection.md:
--------------------------------------------------------------------------------
1 | # Enable firewall protection for KMS
2 |
3 | Feature: Enable firewall protection for KMS
4 | In order to protect KMS
5 | As an engineer
6 | I want to allow traffic to KMS only from Kamus
7 |
8 | Scenario: A KMS encrypt/decrypt request
9 | Given firewall protection enabled on KMS
10 | When a request is sent not from Kamus
11 | Then the request will be denied
12 |
13 | ## Remarks
14 |
15 | * Mitigates:
16 | * [Accessing KMS with leaked credentials](/docs/threatmodeling/threats/kms/leaked_credentials)
17 | * [Exposing names of namespaces and service accounts](/docs/threatmodeling/threats/encryption/namespace_enumeration)
18 | * References:
19 | * https://docs.microsoft.com/en-us/azure/key-vault/key-vault-network-securit
20 |
21 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
22 |
--------------------------------------------------------------------------------
/init-container/tests/Wiremock/mappings/api_v2_decrypt-c5291391-7520-4d8d-a39a-9466ceb759e4.json:
--------------------------------------------------------------------------------
1 | {
2 | "id" : "c5291391-7520-4d8d-a39a-9466ceb759e3",
3 | "name" : "api_v1_decrypt_2",
4 | "request" : {
5 | "url" : "/api/v1/decrypt",
6 | "method" : "POST",
7 | "headers": {
8 | "Authorization": {
9 | "equalTo": "Bearer some-jwt"
10 | }
11 | },
12 | "bodyPatterns" : [ {
13 | "equalToJson" : "{\"data\":\"value\"}\n",
14 | "ignoreArrayOrder" : true,
15 | "ignoreExtraElements" : true
16 | } ]
17 | },
18 | "response" : {
19 | "status" : 200,
20 | "body" : "super-secret-from-value",
21 | "headers" : {
22 | "Content-Type" : "application/json",
23 | "Date" : "Thu, 12 Jul 2018 05:20:33 GMT"
24 | }
25 | },
26 | "uuid" : "c5291391-7520-4d8d-a39a-9466ceb759e3",
27 | "persistent" : true,
28 | "insertionIndex" : 2
29 | }
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/threats/kms/quantom_computing.md:
--------------------------------------------------------------------------------
1 | # Breaking encryption key
2 |
3 | Feature: Breaking encryption key
4 | In order to decrypt pod's secrets
5 | As an attacker
6 | I want to find the private key from the public key
7 |
8 | Scenario Outline: Using Shor's algorithm
9 | Given a client with acess to a public key used by Kamus
10 | And a quantom-powered computer
11 | When the attacker use Shor's algorithm
12 | Then the attacker can find the matching private key
13 |
14 | Examples: Data types
15 | | data-type |
16 | | password |
17 | | API key |
18 | | X.509 private key |
19 | | SSH private key |
20 |
21 | ## Remarks
22 |
23 | * Controls:
24 | * References:
25 | * https://en.wikipedia.org/wiki/Shor%27s_algorithm
26 |
27 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/images/diagram-crd.pu:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | actor User
4 | participant EncryptApi
5 | participant KMS
6 | participant CRDController
7 | participant KubernetesApi
8 |
9 | == Encryption ==
10 |
11 | autonumber
12 | User -> EncryptApi: Encrypt - data, namespace, default service account
13 | EncryptApi -> KMS: Encrypt - data, namespace, default service account
14 | KMS -> EncryptApi: Enrypted data
15 | EncryptApi -> User: Enrypted data
16 |
17 | == Decryption ==
18 |
19 | autonumber 1
20 | CRDController -> KubernetesApi: Watch KamusSecret Objects
21 | User -> KubernetesApi: Create KamusSecret - encrypted data, namesapce
22 | KubernetesApi -> User: Success
23 | KubernetesApi -> CRDController: KamusSecret created
24 | CRDController -> KMS: Decrypt data
25 | KMS -> CRDController: Decrypted data
26 | CRDController -> KubernetesApi: Create secret - decrypted data, namespace
27 |
28 | @enduml
--------------------------------------------------------------------------------
/cli/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": false,
4 | "node": true
5 | },
6 | "extends": [
7 | "eslint:recommended",
8 | "plugin:security/recommended"
9 | ],
10 | "parserOptions": {
11 | "ecmaVersion": 2018
12 | },
13 | "plugins": [
14 | "security"
15 | ],
16 | "rules": {
17 | "linebreak-style": [
18 | "off",
19 | "windows"
20 | ],
21 | "quotes": [
22 | "error",
23 | "single"
24 | ],
25 | "semi": [
26 | "error",
27 | "always"
28 | ],
29 | "no-console": [
30 | "off"
31 | ]
32 | },
33 | "globals": {
34 | "it": true,
35 | "after": true,
36 | "afterEach": true,
37 | "before": true,
38 | "beforeEach": true,
39 | "describe": true
40 | }
41 | }
--------------------------------------------------------------------------------
/tests/blackbox/compose/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | encryptor:
4 | image: $ENCRYPTOR_IMAGE
5 | environment:
6 | - ASPNETCORE_ENVIRONMENT=Development
7 | decryptor:
8 | image: $DECRYPTOR_IMAGE
9 | environment:
10 | - ASPNETCORE_ENVIRONMENT=Development
11 | - Kubernetes__ProxyUrl=http://wiremock:8080
12 |
13 | black-box:
14 | build:
15 | context: ../
16 | environment:
17 | - ENCRYPTOR=http://encryptor:9999/
18 | - DECRYPTOR=http://decryptor:9999/
19 | - TEAMCITY_PROJECT_NAME=api
20 | - PROXY_URL=http://zap:8090
21 | - KUBERNETES_URL=http://wiremock:8080
22 | volumes:
23 | - ./reports:/reports
24 |
25 | wiremock:
26 | build:
27 | context: ../Wiremock
28 |
29 | zap:
30 | image: soluto/zap-ci:1551816665660
31 | logging:
32 | driver:
33 | none
34 |
--------------------------------------------------------------------------------
/init-container/tests/Wiremock/mappings/api_v3_decrypt-c5291391-7520-4d8d-a39a-9466ceb759e4.json:
--------------------------------------------------------------------------------
1 | {
2 | "id" : "c5291391-7520-4d8d-a39a-9466ceb759e2",
3 | "name" : "api_v1_decrypt_3",
4 | "request" : {
5 | "url" : "/api/v1/decrypt",
6 | "method" : "POST",
7 | "headers": {
8 | "Authorization": {
9 | "equalTo": "Bearer some-jwt"
10 | }
11 | },
12 | "bodyPatterns" : [ {
13 | "equalToJson" : "{\"data\":\"{\\\"key\\\":\\\"value\\\"}\"}\n",
14 | "ignoreArrayOrder" : true,
15 | "ignoreExtraElements" : true
16 | } ]
17 | },
18 | "response" : {
19 | "status" : 200,
20 | "body" : "{\"secret_key\": \"secret_value\"}",
21 | "headers" : {
22 | "Content-Type" : "application/json",
23 | "Date" : "Thu, 12 Jul 2018 05:20:33 GMT"
24 | }
25 | },
26 | "uuid" : "c5291391-7520-4d8d-a39a-9466ceb759e3",
27 | "persistent" : true,
28 | "insertionIndex" : 2
29 | }
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/encryption/ip_throttling.md:
--------------------------------------------------------------------------------
1 | # Throttling requests by source IP
2 |
3 | Feature: Throttling requests by source IP
4 | In order to protect Kamus from DoS
5 | As an engineer
6 | I want to throttle incoming requests
7 |
8 | Scenario: Using Nginx Ingress
9 | Given an attacker sending multiple requests
10 | When the limit of allowed request breached
11 | Then Nginx will deny these requests from hitting Kamus
12 |
13 | ## Remarks
14 |
15 | * Mitigates:
16 | * [Kamus server disrupting](/docs/threatmodeling/threats/encryption/denial_of_service)
17 | * [Exposing names of namespaces and service accounts](/docs/threatmodeling/threats/encryption/namespace_enumeration)
18 | * References:
19 | * https://github.com/Shopify/ingress/blob/master/docs/user-guide/nginx-configuration/annotations.md#rate-limiting
20 |
21 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
22 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/decryption/deny_secret_view.md:
--------------------------------------------------------------------------------
1 | # Deny Kuberentes secrets view permissions
2 |
3 | Feature: Deny Kuberentes secrets view permissions
4 | In order to protect Kamus from DoS
5 | As a cluster admin
6 | I want to deny all users from getting Kubernetes secrets in all namespaces
7 |
8 | Scenario: Using Kubernetes RBAC
9 | Given a user role without secrets get permissions
10 | When the user try to read service account's secret
11 | Then the operation denied
12 |
13 |
14 | ## Remarks
15 |
16 | * Mitigates:
17 | * [Impersonating pod to decrypt it's secrets](/docs/threatmodeling/threats/decryption/pod_impersonation)
18 | * [Using KamusSecret to decrypt secrets](/docs/threatmodeling/threats/decryption/leveraging_crd)
19 | * References:
20 | * https://kubernetes.io/docs/reference/access-authn-authz/rbac/
21 |
22 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
23 |
--------------------------------------------------------------------------------
/tests/blackbox/Utils/HttpClientProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Http;
4 |
5 | namespace blackbox.utils
6 | {
7 | public interface IHttpClientProvider
8 | {
9 | HttpClient Provide();
10 | }
11 | public class HttpClientProvider : IHttpClientProvider
12 | {
13 | public HttpClientProvider()
14 | {
15 | ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
16 | }
17 | public HttpClient Provide()
18 | {
19 | var proxyUrl = Environment.GetEnvironmentVariable("PROXY_URL");
20 | var handler = new HttpClientHandler();
21 |
22 | if (proxyUrl != null)
23 | {
24 | handler.Proxy = new WebProxy(proxyUrl, false);
25 | }
26 |
27 | return new HttpClient(handler);
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/init-container/tests/Wiremock/mappings/api_v4_decrypt-c5291391-7520-4d8d-a39a-9466ceb759e4.json:
--------------------------------------------------------------------------------
1 | {
2 | "id" : "c5291391-7520-4d8d-a39a-9466ceb759e5",
3 | "name" : "api_v1_decrypt_4",
4 | "request" : {
5 | "url" : "/api/v1/decrypt",
6 | "method" : "POST",
7 | "headers": {
8 | "Authorization": {
9 | "equalTo": "Bearer some-jwt"
10 | }
11 | },
12 | "bodyPatterns" : [ {
13 | "equalToJson" : "{\"data\":\"{\\\"key-newlines\\\":\\\"value\\\\nvalue\\\"}\"}\n",
14 | "ignoreArrayOrder" : true,
15 | "ignoreExtraElements" : true
16 | } ]
17 | },
18 | "response" : {
19 | "status" : 200,
20 | "body" : "{\"secret_key\": \"secret_value\\nsecret_value\"}",
21 | "headers" : {
22 | "Content-Type" : "application/json",
23 | "Date" : "Thu, 12 Jul 2018 05:20:33 GMT"
24 | }
25 | },
26 | "uuid" : "c5291391-7520-4d8d-a39a-9466ceb759e5",
27 | "persistent" : true,
28 | "insertionIndex" : 2
29 | }
--------------------------------------------------------------------------------
/tests/blackbox/Wiremock/mappings/api_v1_namespaces_default_serviceaccounts_default-223d2c84-2e62-451a-a352-829107a55828.json:
--------------------------------------------------------------------------------
1 | {
2 | "id" : "223d2c84-2e62-451a-a352-829107a55828",
3 | "name" : "api_v1_namespaces_default_serviceaccounts_default",
4 | "request" : {
5 | "url" : "/api/v1/namespaces/default/serviceaccounts/default?exact=true",
6 | "method" : "GET"
7 | },
8 | "response" : {
9 | "status" : 200,
10 | "body" : "{\"kind\":\"ServiceAccount\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"default\",\"namespace\":\"default\",\"selfLink\":\"/api/v1/namespaces/default/serviceaccounts/default\",\"uid\":\"53df03aa-3ca7-11e8-8154-080027c35eca\",\"resourceVersion\":\"44\",\"creationTimestamp\":\"2018-04-10T10:09:59Z\"},\"secrets\":[{\"name\":\"default-token-nnjkh\"}]}\n",
11 | "headers" : {
12 | "Content-Type" : "application/json",
13 | "Date" : "Thu, 12 Jul 2018 05:20:33 GMT"
14 | }
15 | },
16 | "uuid" : "223d2c84-2e62-451a-a352-829107a55828",
17 | "persistent" : true,
18 | "insertionIndex" : 1
19 | }
--------------------------------------------------------------------------------
/tests/blackbox/MonitoringControllerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using blackbox.utils;
5 | using Xunit;
6 |
7 | namespace blackbox
8 | {
9 | public class MonitoringControllerTests {
10 | public IHttpClientProvider mHttpClientProvider { get; set; }
11 | public MonitoringControllerTests () {
12 | mHttpClientProvider = new HttpClientProvider ();
13 |
14 | }
15 |
16 | [Theory]
17 | [InlineData("ENCRYPTOR")]
18 | [InlineData("DECRYPTOR")]
19 | public async Task Test_IsAlive_ReturnsTrue(string configurationName)
20 | {
21 | var client = new HttpClient();
22 | var result = await client.GetAsync(ConfigurationProvider.Configuration[configurationName] + "api/v1/isAlive");
23 |
24 | result.EnsureSuccessStatusCode();
25 |
26 | var content = await result.Content.ReadAsStringAsync();
27 |
28 | Assert.Equal("true", content);
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/threats/kms/leaked_credentials.md:
--------------------------------------------------------------------------------
1 | # Accessing KMS with leaked credentials
2 |
3 | Feature: Accessing KMS with leaked credentials
4 | In order to decrypt pod's secrets
5 | As an attacker
6 | I can access KMS directly
7 |
8 | Scenario Outline: Used leaked credentials
9 | Given KMS compromised
10 | When the attacker call KMS with these credentials
11 | Then the attacker can decrypt any pod secrets, until the credentials expired
12 |
13 | Examples: Data types
14 | | data-type |
15 | | password |
16 | | API key |
17 | | X.509 private key |
18 | | SSH private key |
19 |
20 | ## Remarks
21 |
22 | * Controls:
23 | * [Enable firewall protection for KMS](/docs/threatmodeling/controls/kms/firewall_protection)
24 | * [Credentials Hardening](/docs/threatmodeling/controls/kms/hardening_credentials)
25 | * [Use KMS with HSM support](/docs/threatmodeling/controls/kms/use_hsm)
26 | * References:
27 |
28 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
29 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env
2 |
3 | ARG PROJECT_NAME=decrypt-api
4 |
5 | WORKDIR /app
6 |
7 | # Copy csproj and restore as distinct layers
8 | COPY ./src/$PROJECT_NAME/$PROJECT_NAME.csproj ./$PROJECT_NAME/$PROJECT_NAME.csproj
9 | COPY ./src/key-managment/key-managment.csproj ./key-managment/key-managment.csproj
10 | RUN dotnet restore $PROJECT_NAME/$PROJECT_NAME.csproj
11 |
12 | # Copy everything else and build
13 | COPY ./src/$PROJECT_NAME ./$PROJECT_NAME
14 | COPY ./src/key-managment ./key-managment
15 | RUN dotnet publish $PROJECT_NAME/$PROJECT_NAME.csproj -c Release -o ./obj/Docker/publish
16 |
17 | # Build runtime image
18 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS release
19 | ARG PROJECT_NAME=decrypt-api
20 | ENV PROJECT_NAME_ENV=$PROJECT_NAME
21 | RUN groupadd dotnet && \
22 | useradd dotnet -g dotnet --home /home/dotnet
23 |
24 | USER dotnet
25 |
26 | WORKDIR /home/dotnet/app
27 | ENV ASPNETCORE_URLS=http://+:9999
28 | COPY --from=build-env /app/obj/Docker/publish .
29 |
30 | ENTRYPOINT dotnet $PROJECT_NAME_ENV.dll
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/threats/encryption/sniffing_user_traffic.md:
--------------------------------------------------------------------------------
1 | # Sniffing user's traffic
2 |
3 | Feature: Sniffing user's traffic
4 | In order to find information
5 | As an attacker
6 | I want to sniff user's traffic
7 |
8 | Scenario Outline: Sniffing encryption request
9 | Given a compromised user device
10 | When the user send an encryption request to Kamus
11 | Then the attacker can inspect the traffic and retrieve the secret sent to Kamus
12 |
13 | Examples: Data types
14 | | Namespace |
15 | | Service account |
16 | | Teams |
17 | | Partners |
18 | | Bussiness information |
19 |
20 | ## Remarks
21 |
22 | * Controls:
23 | * [Certificate pinning](/docs/threatmodeling/controls/encryption/certificate_pinning)
24 | * [Client-side encryption](/docs/threatmodeling/controls/encryption/client_side_encryption)
25 | * References:
26 | * https://en.wikipedia.org/wiki/Man-in-the-middle_attack
27 |
28 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
--------------------------------------------------------------------------------
/site/layouts/partials/header.html:
--------------------------------------------------------------------------------
1 |
2 | {{ .Site.Title }}
3 | {{ partialCached "inlinecss.html" . }}
4 | {{ partialCached "inlinescript.html" . }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/threats/encryption/denial_of_service.md:
--------------------------------------------------------------------------------
1 | # Kamus server disrupting
2 |
3 | Feature: Kamus server disrupting
4 | In order to distrupt Kamus server
5 | As an attacker
6 | I want to load it with requests
7 |
8 | Scenario: Encryption requests
9 | Given a name of a namespace and a service account
10 | When the attacker send massive amount of encrypt request
11 | Then the attacker can distrupt the service
12 |
13 | ## Remarks
14 |
15 | * Controls:
16 | * [Block anonymous internet access to Kamus](/docs/threatmodeling/controls/encryption/block_internet_access)
17 | * [Deny request for default SA](/docs/threatmodeling/controls/encryption/deny_default_sa)
18 | * [Client-side encryption](/docs/threatmodeling/controls/encryption/client_side_encryption)
19 | * [Throttling requests by source IP](/docs/threatmodeling/controls/encryption/ip_throttling)
20 | * References:
21 | * https://en.wikipedia.org/wiki/Denial-of-service_attack
22 | * https://docs.microsoft.com/en-us/azure/key-vault/key-vault-service-limits
23 |
24 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: 'bug :bug:'
6 | assignees: ''
7 |
8 | ---
9 |
10 | **General remarks**
11 |
12 | > Please delete this section including header before submitting
13 | >
14 | > This form is to report bugs. For general usage questions refer to
15 | > [Kamus slack].
16 | >
17 | > If you want to propose an enhancement or feature request, please use the feature request template.
18 |
19 |
20 | **Describe the bug**
21 | A clear and concise description of what the bug is.
22 |
23 | **Versions used**
24 | Kamus (API images):
25 | Kamus CLI:
26 | Chart version:
27 | KMS provider:
28 | Kubernetes flavour and version: (e.g. OpenShift Origin 3.9)
29 |
30 | **To Reproduce**
31 | Steps to reproduce the behavior:
32 | 1.
33 | 2.
34 | 3.
35 | 4.
36 |
37 | **Expected behavior**
38 | A clear and concise description of what you expected to happen.
39 |
40 | [Kamus slack]: (https://join.slack.com/t/k8s-kamus/shared_invite/enQtODA2MjI3MjAzMjA1LThlODkxNTg3ZGVmMjVkOTBhY2RmMmRjOWFiOGU2NzQ1ODU4ODNiMDJiZTE5ZTY4YmRiOTM3MjI0MDc0OGFkN2E)
--------------------------------------------------------------------------------
/tests/blackbox/Utils/baerer/JwtSignInHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IdentityModel.Tokens.Jwt;
3 | using System.Security.Claims;
4 | using Microsoft.IdentityModel.Tokens;
5 |
6 | namespace blackbox.utils.baerer
7 | {
8 | public class JwtSignInHandler
9 | {
10 | public const string TokenAudience = "Myself";
11 | public const string TokenIssuer = "MyProject";
12 | private readonly SymmetricSecurityKey key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("mysupersecret_secretkey!123"));
13 |
14 | public string BuildJwt(ClaimsPrincipal principal)
15 | {
16 | var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
17 |
18 | var token = new JwtSecurityToken(
19 | issuer: TokenIssuer,
20 | audience: TokenAudience,
21 | claims: principal.Claims,
22 | expires: DateTime.Now.AddMinutes(20),
23 | signingCredentials: creds
24 | );
25 |
26 | return new JwtSecurityTokenHandler().WriteToken(token);
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/site/layouts/partials/sidebar.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/crd-controller/key.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC6zCCAdOgAwIBAgIJALnL4xBfnySQMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV
3 | BAMMDGFkbWlzc2lvbl9jYTAgFw0xOTA2MzAwNTQwMzZaGA8yMjkzMDQxNDA1NDAz
4 | NlowFjEUMBIGA1UEAwwLb3BhLm9wYS5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IB
5 | DwAwggEKAoIBAQC/ucD4yn/2gkHAtSG/sC/jpkMZj4+YFr4tA7sqFnHAiT4feZA5
6 | iwGimn0CpTBHzsqSSCPfQ3VlQYQV8dZbhFl651N9Hn72CnvWuywGHPhnA4vhNfzj
7 | XkzVzc234xx4nUUSdo+3wj5L1o8+oR7jFneAsoXdU95kpG0HGdCXl1HC/7coTQYz
8 | k8irqiIGfiQkTWwmwCyC8pn7yaxoGUfo8WV5xNilUh71acK6EEsJlBp4FWVlHzmv
9 | jIctEJbP4utXA2wHrRyqZc6biEUyz3naoQTReGN1bi6F4MAUx+9R3GuxI3/TwbPt
10 | X2W9hxuuCqrc+87GJg2C/xwb5tsEob7FcaanAgMBAAGjOTA3MAkGA1UdEwQCMAAw
11 | CwYDVR0PBAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATANBgkq
12 | hkiG9w0BAQUFAAOCAQEAVHUE6VvsYfwInjdmgpqZyaMGSCMzsoInD+MWZ27O5P4T
13 | CMjMmBhQpUXDaSRXKp/8mZTMsSyD01XtJ86JaLITpisURtdsTjCB07BdQUvpTP7Y
14 | +YFLGjHZVcrJiqwFYlNYEZVc2gFOLUPzxXubiVnGS80kHCbkKQNmQp1eXxR0dqbV
15 | +o5ZOe+3jQ8Io2uO9meBpZ/A3FAjm8lvAZgitgNF9DFlBf2hGlP2wRmDajHuDolY
16 | H8DZkwnoxi8XFoBgzIw1xPgdUH79xq577lILpejKeGiVc3ypaaBTGCWLIG23ukjJ
17 | HytqWPs1PUFoq8itn90epbiqKs8K5NhlVa5Id3MYsA==
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/threats/decryption/sniffing_tampering.md:
--------------------------------------------------------------------------------
1 | # Sniff requests and responses to Kamus
2 |
3 | Feature: Sniff requests and responses to Kamus
4 | In order to compromise secrets
5 | As an attacker
6 | I want to sniff requests and response to Kamus
7 |
8 | Scenario Outline: MitM
9 | Given a compromised Kubernetes node
10 | When the attacker sniff all the network traffic in this node
11 | Then the attacker can view reqeusts and responses to Kamus
12 |
13 | Examples: Data types
14 | | data-type |
15 | | password |
16 | | API key |
17 | | X.509 private key |
18 | | SSH private key |
19 | | Service account toekns |
20 |
21 | ## Remarks
22 |
23 | * Controls:
24 | * [Serve Kamus API over TLS](/docs/threatmodeling/controls/decryption/kamus_in_cluster_tls)
25 | * [Use a policy to control pod's service account](/docs/threatmodeling/controls/decryption/opa_pods_secrets)
26 | * References:
27 | * https://en.wikipedia.org/wiki/Man-in-the-middle_attack
28 |
29 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
--------------------------------------------------------------------------------
/site/content/docs/contributing/roadmap.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Roadmap"
3 | menu:
4 | main:
5 | parent: "contributing"
6 | identifier: "roadmap"
7 | weight: 3
8 | ---
9 | # Roadmap 🗺️
10 |
11 | This document outlines the features we plan to work on in the near future:
12 |
13 | - [ ] Support for non-kubernetes deployments (#112)
14 | - [X] Templating support for init container (#141, #202)
15 | - [ ] Automatic init container injection (#155)
16 | - [ ] Stability, improve tests coverage and other non-functional tasks (there are multiple issues around this area)
17 |
18 | Other goals / tasks not listed here can be found in the [GitHub issues].
19 |
20 | Looking for good starting point? look for issues labeled [good first issue] or [help wanted]!
21 |
22 | [#112]: https://github.com/Soluto/kamus/issues/112
23 | [#141]: https://github.com/Soluto/kamus/issues/141
24 | [#155]: https://github.com/Soluto/kamus/issues/155
25 | [#202]: https://github.com/Soluto/kamus/issues/202
26 | [good first issue]: https://github.com/Soluto/kamus/labels/good%20first%20issue
27 | [help wanted]: https://github.com/Soluto/kamus/labels/help%20wanted
28 | [GitHub issues]: https://github.com/Soluto/Kamus/issues
29 |
--------------------------------------------------------------------------------
/tests/integration/integration.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.0
4 | false
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Always
22 |
23 |
24 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/threats/decryption/leveraging_crd.md:
--------------------------------------------------------------------------------
1 | # Using KamusSecret to decrypt secrets
2 |
3 | Feature: Using KamusSecret to decrypt secrets
4 | In order to decrypt pod's secrets
5 | As an attacker
6 | I want to create KamusSecret
7 |
8 | Scenario Outline: Creating KamusSecret
9 | Given a user with permissions to create KamusSecret and get Kubernetes Secrets
10 | When the user create a KamusSecret with a pod's secrets from the same namespace
11 | Then the user can get the created Kubernetes Secret and read the decrypted secrets
12 |
13 | Examples: Data types
14 | | data-type |
15 | | password |
16 | | API key |
17 | | X.509 private key |
18 | | SSH private key |
19 |
20 | Scenario: Service accounts mount
21 | Given a permission to create a pod
22 | When the attacker launch a pod with anther pod's service account
23 | Then the attacker can use the token for authentication and decrypt the secrets
24 |
25 | ## Remarks
26 |
27 | * Controls:
28 | * [Deny Kuberentes secrets view permissions](/docs/threatmodeling/controls/decryption/deny_secret_view)
29 | * References:
30 |
31 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/decryption/opa_pods_secrets.md:
--------------------------------------------------------------------------------
1 | # Use a policy to control pod's service account
2 |
3 | Feature: Use a policy to control pod's service account
4 | In order to protect pods from impersonation
5 | As a cluster admin
6 | I want to use a policy to define which pods can use which service account
7 |
8 | Scenario: Using Azure OPA
9 | Given a pod A
10 | And a service account B is used by another pod B
11 | When the user try mount service account B to pod A
12 | Then the operation denied
13 |
14 | Scenario: Using Kubernetes addmission controller
15 | Given a pod A
16 | And a service account B is used by another pod B
17 | When the user try mount service account B to pod A
18 | Then the operation denied
19 |
20 |
21 | ## Remarks
22 |
23 | * Mitigates:
24 | * [Impersonating pod to decrypt it's secrets](/docs/threatmodeling/threats/decryption/pod_impersonation)
25 | * [Sniff requests and responses to Kamus](/docs/threatmodeling/threats/decryption/sniffing_tampering)
26 | * References:
27 | * https://stackoverflow.com/questions/50893535/securing-kubernetes-service-with-tls
28 | * https://istio.io/docs/tasks/security/https-overlay/
29 |
30 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
31 |
--------------------------------------------------------------------------------
/src/crd-controller/utils/KeyManagementExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Kamus.KeyManagement;
5 |
6 | namespace CustomResourceDescriptorController.utils
7 | {
8 | public static class KeyManagementExtensions
9 | {
10 | public async static Task> DecryptItems(
11 | this IKeyManagement keyManagement,
12 | Dictionary source,
13 | string serviceAccountId,
14 | Action errorHandler,
15 | Func mapper)
16 | {
17 | var result = new Dictionary();
18 |
19 | if (source == null)
20 | {
21 | return result;
22 | }
23 |
24 | foreach (var (key, value) in source)
25 | {
26 | try
27 | {
28 | var decrypted = await keyManagement.Decrypt(value, serviceAccountId);
29 |
30 | result.Add(key, mapper(decrypted));
31 | }
32 | catch (Exception e)
33 | {
34 | errorHandler(e, key);
35 | }
36 | }
37 |
38 | return result;
39 | }
40 |
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/site/config.toml:
--------------------------------------------------------------------------------
1 | title = "kamus"
2 | baseURL = "https://kamus.soluto.io"
3 | languageCode = "en-us"
4 |
5 | # we use this to disable indexing for the non-production build
6 | enableRobotsTXT = true
7 |
8 | # this allows us to show the source commit in the footer
9 | enableGitInfo = true
10 |
11 | # syntax highlighting options
12 | pygmentsCodeFences = true
13 | pygmentsStyle = "tango"
14 |
15 | # we don't use these currently
16 | disableKinds = ["taxonomy", "taxonomyTerm"]
17 |
18 | # enable hugo's menu system for the site, name the primary menu
19 | sectionPagesMenu = "main"
20 | # menu entries
21 | [menu]
22 | [[menu.main]]
23 | identifier = "home"
24 | name = "Home"
25 | title = "Home"
26 | url = "/"
27 | weight = 1
28 | [[menu.main]]
29 | identifier = "user"
30 | name = "User Guide"
31 | title = "User Guide"
32 | weight = 2
33 | [[menu.main]]
34 | identifier = "contributing"
35 | name = "Contributing"
36 | title = "contributing"
37 | url = "/docs/contributing/"
38 | weight = 6
39 |
40 | # enable auto-generated _redirects file
41 | [mediaTypes."text/netlify"]
42 | delimiter = ""
43 |
44 | [outputFormats.REDIRECTS]
45 | mediaType = "text/netlify"
46 | baseName = "_redirects"
47 |
48 | [outputs]
49 | home = ["HTML", "REDIRECTS"]
50 |
51 | [markup.goldmark.renderer]
52 | unsafe= true
53 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: 'enhancement :rocket:'
6 | assignees: ''
7 |
8 | ---
9 |
10 | **General remarks**
11 |
12 | > Please delete this section including header before submitting
13 | >
14 | > This form is to report feature requests. For general usage questions refer to
15 | > [Kamus slack].
16 | >
17 | > If you want to report a bug, please use the bug report template.
18 |
19 | **Is your feature request related to a problem? Please describe.**
20 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
21 |
22 | **Describe the solution you'd like**
23 | A clear and concise description of what you want to happen.
24 |
25 | **Describe alternatives you've considered**
26 | A clear and concise description of any alternative solutions or features you've considered.
27 |
28 | **Additional context**
29 | Add any other context or screenshots about the feature request here. If it is about a new design, please attach a drawing of a potential solution.
30 | If the feature is related to a specific KMS provider, please specify that.
31 |
32 | [Kamus slack]: (https://join.slack.com/t/k8s-kamus/shared_invite/enQtODA2MjI3MjAzMjA1LThlODkxNTg3ZGVmMjVkOTBhY2RmMmRjOWFiOGU2NzQ1ODU4ODNiMDJiZTE5ZTY4YmRiOTM3MjI0MDc0OGFkN2E)
--------------------------------------------------------------------------------
/tests/blackbox/Wiremock/mappings/apis_authorizationk8sio_v1_selfsubjectaccessreviews-b94a1942-1c74-40cd-9136-0df1d2217e77.json:
--------------------------------------------------------------------------------
1 | {
2 | "id" : "b94a1942-1c74-40cd-9136-0df1d2217e77",
3 | "name" : "apis_authorizationk8sio_v1_selfsubjectaccessreviews",
4 | "request" : {
5 | "url" : "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews",
6 | "method" : "POST",
7 | "bodyPatterns" : [ {
8 | "equalToJson" : "{\n \"apiVersion\": \"authorization.k8s.io/v1\",\n \"kind\": \"SelfSubjectAccessReview\",\n \"spec\": {\n \"resourceAttributes\": {\n \"group\": \"authentication.k8s.io\",\n \"resource\": \"tokenreviews\",\n \"verb\": \"create\"\n }\n }\n}",
9 | "ignoreArrayOrder" : true,
10 | "ignoreExtraElements" : true
11 | } ]
12 | },
13 | "response" : {
14 | "status" : 201,
15 | "body" : "{\"kind\":\"SelfSubjectAccessReview\",\"apiVersion\":\"authorization.k8s.io/v1\",\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"resourceAttributes\":{\"verb\":\"create\",\"group\":\"authentication.k8s.io\",\"resource\":\"tokenreviews\"}},\"status\":{\"allowed\":true}}\n",
16 | "headers" : {
17 | "Content-Type" : "application/json",
18 | "Date" : "Mon, 21 Jan 2019 06:20:20 GMT"
19 | }
20 | },
21 | "uuid" : "b94a1942-1c74-40cd-9136-0df1d2217e77",
22 | "persistent" : true,
23 | "insertionIndex" : 3
24 | }
--------------------------------------------------------------------------------
/src/key-managment/key-managment.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | key_managment
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/deployment-kamus/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | name: kamus-example
5 | labels:
6 | app: kamus-example
7 | spec:
8 | template:
9 | metadata:
10 | labels:
11 | app: kamus-example
12 | spec:
13 | serviceAccountName: kamus-example-sa
14 | automountServiceAccountToken: true
15 | initContainers:
16 | - name: "kamus-init"
17 | image: "ghcr.io/soluto/kamus-init-container:latest"
18 | imagePullPolicy: IfNotPresent
19 | env:
20 | - name: KAMUS_URL
21 | value: http://kamus-decryptor.default.svc.cluster.local/
22 | volumeMounts:
23 | - name: encrypted-secrets
24 | mountPath: /encrypted-secrets
25 | - name: decrypted-secrets
26 | mountPath: /decrypted-secrets
27 | args: ["-e","/encrypted-secrets","-d","/decrypted-secrets", "-n", "config.json"]
28 | containers:
29 | - name: app
30 | image: soluto/kamus-example-app
31 | imagePullPolicy: IfNotPresent
32 | volumeMounts:
33 | - name: decrypted-secrets
34 | mountPath: /secrets
35 | volumes:
36 | - name: encrypted-secrets
37 | configMap:
38 | name: encrypted-secrets-cm
39 | - name: decrypted-secrets
40 | emptyDir:
41 | medium: Memory
42 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/controls/encryption/block_internet_access.md:
--------------------------------------------------------------------------------
1 | # Block anonymous internet access to Kamus
2 |
3 | Feature: Block anonymous internet access to Kamus
4 | In order to protect Kamus from DoS
5 | As an engineer
6 | I want to block anonymous access to Kamus from the internet
7 |
8 |
9 | Scenario: Use Kubernetes port-forward
10 | Given a user has valid Kubernetes config file
11 | When the user open port forward to Kamus service
12 | Then the user can use Kamus for encryption
13 |
14 | Scenario: User authentication
15 | Given an authenticated user (using Kubernetes user token?)
16 | When the user try to encrypt a secret
17 | Then the user is allowed to perform the request
18 |
19 | Scenario: IP filtering
20 | Given an authenticated user (using Kubernetes user token?)
21 | When the user try to encrypt a secret
22 | Then the user is allowed to perform the request only from a white-listed IP
23 |
24 |
25 | ## Remarks
26 |
27 | * Mitigates:
28 | * [Kamus server disrupting](/docs/threatmodeling/threats/encryption/denial_of_service)
29 | * [Exposing names of namespaces and service accounts](/docs/threatmodeling/threats/encryption/namespace_enumeration)
30 | * References:
31 | * https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/
32 |
33 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
34 |
--------------------------------------------------------------------------------
/src/crd-controller/utils/KubernetesExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive.Linq;
3 | using System.Threading;
4 | using k8s;
5 |
6 | namespace CustomResourceDescriptorController.utils
7 | {
8 | public static class KubernetesExtensions
9 | {
10 | public static IObservable<(WatchEventType, TCRD)> ObserveClusterCustomObject(
11 | this IKubernetes kubernetes,
12 | string group,
13 | string version,
14 | string plural,
15 | CancellationToken cancellationToken
16 | ) where TCRD : class
17 | {
18 | return Observable.FromAsync(async () =>
19 | {
20 | var subject = new System.Reactive.Subjects.Subject<(WatchEventType, TCRD)>();
21 | var path = $"apis/{group}/{version}/watch/{plural}";
22 | await kubernetes.WatchObjectAsync(path,
23 | timeoutSeconds: int.MaxValue,
24 | onEvent: (@type, @event) => subject.OnNext((@type, @event)),
25 | onError: e => subject.OnError(e),
26 | onClosed: () => subject.OnCompleted(), cancellationToken: cancellationToken);
27 | return subject;
28 | })
29 | .SelectMany(x => x)
30 | .Select(t => (t.Item1, t.Item2 as TCRD))
31 | .Where(t => t.Item2 != null);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/threats/encryption/namespace_enumeration.md:
--------------------------------------------------------------------------------
1 | # Exposing names of namespaces and service accounts
2 |
3 | Feature: Exposing names of namespaces and service accounts
4 | In order to find information
5 | As an attacker
6 | I want to enumarate all possible combinations of namespaces and service accounts
7 |
8 | Scenario Outline: Enumeration of all possible combinations
9 | Given a list of all possible combinations
10 | And the attacker send encrypt request for each combination
11 | When the attacker receive sucess response
12 | Then the attacker know the combination exists on the cluster
13 |
14 | Examples: Data types
15 | | Namespace |
16 | | Service account |
17 | | Teams |
18 | | Partners |
19 | | Bussiness information |
20 |
21 | ## Remarks
22 |
23 | * Controls:
24 | * [Block anonymous internet access to Kamus](/docs/threatmodeling/controls/encryption/block_internet_access)
25 | * [Deny request for default SA](/docs/threatmodeling/controls/encryption/deny_default_sa)
26 | * [Enable firewall protection for KMS](/docs/threatmodeling/controls/kms/firewall_protection)
27 | * References:
28 | * https://en.wikipedia.org/wiki/Denial-of-service_attack
29 | * https://docs.microsoft.com/en-us/azure/key-vault/key-vault-service-limits
30 |
31 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
--------------------------------------------------------------------------------
/tests/crd-controller/crd.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | # name must match the spec fields below, and be in the form: .
5 | name: kamussecrets.soluto.com
6 | spec:
7 | # group name to use for REST API: /apis//
8 | group: soluto.com
9 | # version name to use for REST API: /apis//
10 | versions:
11 | - name: v1alpha2
12 | served: true
13 | storage: true
14 | schema:
15 | openAPIV3Schema:
16 | type: object
17 | properties:
18 | data:
19 | type: object
20 | additionalProperties: true
21 | stringData:
22 | type: object
23 | additionalProperties: true
24 | serviceAccount:
25 | type: string
26 | type:
27 | type: string
28 | propagateAnnotations:
29 | type: boolean
30 | # either Namespaced or Cluster
31 | scope: Namespaced
32 | names:
33 | # plural name to be used in the URL: /apis///
34 | plural: kamussecrets
35 | # singular name to be used as an alias on the CLI and for display
36 | singular: kamussecret
37 | # kind is normally the CamelCased singular type. Your resource manifests use this.
38 | kind: KamusSecret
39 | # shortNames allow shorter string to match your resource on the CLI
40 | shortNames:
41 | - ks
42 |
--------------------------------------------------------------------------------
/tests/blackbox/blackbox.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Always
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/key-managment/EnvelopeEncryptionUtils.cs:
--------------------------------------------------------------------------------
1 | using Optional;
2 | using System;
3 | using System.Text.RegularExpressions;
4 |
5 | namespace Kamus.KeyManagement
6 | {
7 | public static class EnvelopeEncryptionUtils
8 | {
9 | private static readonly Regex WrappedDataRegex = new Regex("env\\$(?.*)\\$(?.*):(?.*)");
10 |
11 | public static string Wrap(string encryptedDataKey, byte[] iv, byte[] encryptedData)
12 | {
13 | return $"env${encryptedDataKey}${Convert.ToBase64String(iv)}:{Convert.ToBase64String(encryptedData)}";
14 | }
15 |
16 | ///
17 | /// Return some with the decryption results if the value is wrapped, otherwise returns none.
18 | ///
19 | /// The unwrap.
20 | /// Wrapped data.
21 | public static Option> Unwrap(string wrappedData)
22 | {
23 | var match = WrappedDataRegex.Match(wrappedData);
24 |
25 | if (!match.Success)
26 | {
27 | return Option.None>();
28 | }
29 |
30 | var encryptedDataKey = match.Groups["encryptedDataKey"].Value;
31 | var actualEncryptedData = Convert.FromBase64String(match.Groups["encryptedData"].Value);
32 | var iv = Convert.FromBase64String(match.Groups["iv"].Value);
33 |
34 | return Option.Some(Tuple.Create(encryptedDataKey, iv, actualEncryptedData));
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/crd-controller/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Cryptography.X509Certificates;
2 | using System.Security.Cryptography;
3 | using App.Metrics.Formatters.Prometheus;
4 | using Microsoft.AspNetCore.Hosting;
5 | using System;
6 | using System.Net;
7 | using System.IO;
8 | using Microsoft.Extensions.Hosting;
9 | using Serilog;
10 |
11 | namespace CustomResourceDescriptorController
12 | {
13 | public class Program
14 | {
15 | public static void Main(string[] args)
16 | {
17 | BuildWebHost(args).Run();
18 | }
19 | public static IHost BuildWebHost(string[] args) =>
20 | Host.CreateDefaultBuilder(args)
21 | .UseMetricsEndpoints(options =>
22 | {
23 | options.MetricsEndpointOutputFormatter = new MetricsPrometheusTextOutputFormatter();
24 | options.MetricsTextEndpointEnabled = false;
25 | options.EnvironmentInfoEndpointEnabled = false;
26 | })
27 | .UseSerilog()
28 | .ConfigureWebHostDefaults(
29 | webBuilder =>
30 | {
31 | webBuilder.UseStartup();
32 | webBuilder.ConfigureKestrel((context, options) =>
33 | {
34 | options.Listen(IPAddress.Any, 9999, listenOptions => { });
35 | })
36 | ;
37 | })
38 | .Build();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/crd-controller/LoggingMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.Extensions.Logging;
8 |
9 | namespace CustomResourceDescriptorController
10 | {
11 | public class LoggingMiddleware
12 | {
13 | private readonly RequestDelegate mNext;
14 | private readonly ILogger mLogger;
15 |
16 | public LoggingMiddleware(RequestDelegate next, ILogger logger)
17 | {
18 | mNext = next;
19 | mLogger = logger;
20 | }
21 |
22 | public async Task Invoke(HttpContext httpContext)
23 | {
24 | try
25 | {
26 | await mNext(httpContext);
27 | }catch(Exception e)
28 | {
29 | mLogger.LogError($"Unhandled exception while processing request: {e}");
30 | httpContext.Response.StatusCode = 500;
31 | await httpContext.Response.WriteAsync("server failed to handle response");
32 | }
33 | }
34 | }
35 |
36 | // Extension method used to add the middleware to the HTTP request pipeline.
37 | public static class LoggingMiddlewareExtensions
38 | {
39 | public static IApplicationBuilder UseLoggingMiddleware(this IApplicationBuilder builder)
40 | {
41 | return builder.UseMiddleware();
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/decrypt-api/ErrorHandlingMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace Kamus
8 | {
9 | // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
10 | public class ErrorHandlingMiddleware
11 | {
12 | private readonly RequestDelegate mNext;
13 | private readonly ILogger mLogger;
14 |
15 | public ErrorHandlingMiddleware(RequestDelegate next, ILogger logger)
16 | {
17 | mNext = next;
18 | mLogger = logger;
19 | }
20 |
21 | public async Task Invoke(HttpContext httpContext)
22 | {
23 | try
24 | {
25 | await mNext.Invoke(httpContext);
26 | } catch (Exception e) {
27 | mLogger.LogError(e, $"Unhandled exception while processing request");
28 | httpContext.Response.StatusCode = 500;
29 | await httpContext.Response.WriteAsync("server failed to handle response");
30 | }
31 | }
32 | }
33 |
34 | // Extension method used to add the middleware to the HTTP request pipeline.
35 | public static class ErrorHandlingMiddlewareExtensions
36 | {
37 | public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder builder)
38 | {
39 | return builder.UseMiddleware();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/encrypt-api/ErrorHandlingMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace Kamus
8 | {
9 | // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
10 | public class ErrorHandlingMiddleware
11 | {
12 | private readonly RequestDelegate mNext;
13 | private readonly ILogger mLogger;
14 |
15 | public ErrorHandlingMiddleware(RequestDelegate next, ILogger logger)
16 | {
17 | mNext = next;
18 | mLogger = logger;
19 | }
20 |
21 | public async Task Invoke(HttpContext httpContext)
22 | {
23 | try
24 | {
25 | await mNext.Invoke(httpContext);
26 | } catch (Exception e) {
27 | mLogger.LogError(e, $"Unhandled exception while processing request");
28 | httpContext.Response.StatusCode = 500;
29 | await httpContext.Response.WriteAsync("server failed to handle response");
30 | }
31 | }
32 | }
33 |
34 | // Extension method used to add the middleware to the HTTP request pipeline.
35 | public static class ErrorHandlingMiddlewareExtensions
36 | {
37 | public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder builder)
38 | {
39 | return builder.UseMiddleware();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@soluto-asurion/kamus-cli",
3 | "version": "0.3.6",
4 | "description": "CLI Tool to encrypt secrets for kamus",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "node_modules/.bin/mocha ./test/*.spec.js --exit",
8 | "eslint": "eslint . --ignore-pattern node_modules/",
9 | "snyk-protect": "snyk protect",
10 | "prepublish": "yarn run snyk-protect"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/soluto/kamus.git"
15 | },
16 | "bin": {
17 | "kamus-cli": "lib/index.js"
18 | },
19 | "keywords": [
20 | "Secrets",
21 | "Kubernetes",
22 | "Kamus",
23 | "GitOps"
24 | ],
25 | "author": "Shai Katz",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/soluto/kamus/issues"
29 | },
30 | "homepage": "https://github.com/soluto/kamus#readme",
31 | "dependencies": {
32 | "adal-node": "0.2.1",
33 | "bluebird": "^3.5.3",
34 | "caporal": "^1.1.0",
35 | "colorful-chalk-logger": "^0.4.0",
36 | "enquirer": "^2.3.0",
37 | "node-fetch": "^2.6.1",
38 | "opn": "^5.4.0",
39 | "request": "2.88.2",
40 | "snyk": "1.685.0",
41 | "url-join": "^4.0.1"
42 | },
43 | "devDependencies": {
44 | "chai": "4.2.0",
45 | "eslint": "7.7.0",
46 | "eslint-plugin-security": "1.4.0",
47 | "husky": "4.2.5",
48 | "lint-staged": "10.2.6",
49 | "mocha": "8.0.1",
50 | "mock-fs": "4.12.0",
51 | "nock": "13.0.4",
52 | "sinon": "9.0.3"
53 | },
54 | "files": [
55 | "/lib",
56 | "yarn.lock"
57 | ],
58 | "snyk": true
59 | }
60 |
--------------------------------------------------------------------------------
/tests/crd-controller/crd-controller.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 | crd_controller
6 |
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | PreserveNewest
21 |
22 |
23 | PreserveNewest
24 |
25 |
26 | PreserveNewest
27 |
28 |
29 | PreserveNewest
30 |
31 |
32 | PreserveNewest
33 |
34 |
35 | PreserveNewest
36 |
37 |
38 | PreserveNewest
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/encrypt-api/encrypt-api.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.1
4 |
5 |
6 | 0.9.0.7
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 |
--------------------------------------------------------------------------------
/ci/version_to_deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo checking decryptor api version
4 | DECRYPTOR_API_VERSION=$(grep -E "" ./src/decrypt-api/decrypt-api.csproj | grep -Eo "[0-9.]*(-rc[0-9]*)?")
5 | DECRYPTOR_API_TAG="decryptor-$DECRYPTOR_API_VERSION"
6 | export DECRYPTOR_API_DOCKER_TAG="decryptor-latest"
7 | if [[ "$(git tag | grep -c "$DECRYPTOR_API_TAG")" == "0" ]]; then
8 | echo tagging "$DECRYPTOR_API_TAG"
9 | git tag "$DECRYPTOR_API_TAG"
10 | export DECRYPTOR_API_DOCKER_TAG=$DECRYPTOR_API_TAG
11 | fi
12 |
13 | echo checking encryptor api version
14 | ENCRYPTOR_API_VERSION=$(grep -E "" ./src/encrypt-api/encrypt-api.csproj | grep -Eo "[0-9.]*(-rc[0-9]*)?")
15 | ENCRYPTOR_API_TAG="encryptor-$ENCRYPTOR_API_VERSION"
16 | export ENCRYPTOR_API_DOCKER_TAG="encryptor-latest"
17 | if [[ "$(git tag | grep -c "$ENCRYPTOR_API_TAG")" == "0" ]]; then
18 | echo tagging "$ENCRYPTOR_API_TAG"
19 | git tag "$ENCRYPTOR_API_TAG"
20 | export ENCRYPTOR_API_DOCKER_TAG=$ENCRYPTOR_API_TAG
21 | fi
22 |
23 | echo checking controller api version
24 | CONTROLLER_API_VERSION=$(grep -E "" ./src/crd-controller/crd-controller.csproj | grep -Eo "[0-9.]*(-rc[0-9]*)?")
25 | CONTROLLER_API_TAG="controller-$CONTROLLER_API_VERSION"
26 | export CONTROLLER_API_DOCKER_TAG="controller-latest"
27 | if [[ "$(git tag | grep -c "$CONTROLLER_API_TAG")" == "0" ]]; then
28 | echo tagging "$CONTROLLER_API_TAG"
29 | git tag "$CONTROLLER_API_TAG"
30 | export CONTROLLER_API_DOCKER_TAG=$CONTROLLER_API_TAG
31 | fi
32 |
33 | {
34 | echo "export DECRYPTOR_API_DOCKER_TAG=$DECRYPTOR_API_DOCKER_TAG"
35 | echo "export ENCRYPTOR_API_DOCKER_TAG=$ENCRYPTOR_API_DOCKER_TAG"
36 | echo "export CONTROLLER_API_DOCKER_TAG=$CONTROLLER_API_DOCKER_TAG"
37 | } >> "$BASH_ENV"
--------------------------------------------------------------------------------
/src/decrypt-api/decrypt-api.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.1
4 |
5 |
6 | 0.9.0.7
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 |
--------------------------------------------------------------------------------
/release_management.md:
--------------------------------------------------------------------------------
1 | # Release management
2 | Kamus releases are somewhat complex, we're aware to that.
3 | Each component has it's own versioning, following semantic versioning rules.
4 | Each component version has it's matching tag on git (e.g. `kamus-cli-0.1`) for tracking version history.
5 | We also maintain "artificial releases" - releases that exists on the release page and on the changelog file. Those releases are used to track closed issues and features - make it easier to understand what change in Kamus.
6 |
7 | ## Creating a new release
8 | * Create and push a new tag in the format `kamus-` (Due to [this bug][pr-bug], the latest PR is not included in the release - if you need it, make sure to create a dummy commit after it).
9 | * Create new [personal access token], with `repo` scope.
10 | * Export the token `export GREN_GITHUB_TOKEN=<>`
11 | * Install [gren]
12 | * Create a new release:
13 | ```
14 | gren release --prerelease
15 | ```
16 | * Update changelog file:
17 | ```
18 | gren changelog --override
19 | ```
20 |
21 | * Delete the token when you're done on the [personal access token] page.
22 |
23 | Please note: due to [this issue][issues-bug], gren will fetch only the first 30 issues, if there are more issues - gren will not detect them. There is a partial workaround documented in the issue.
24 |
25 | ### How releases are created?
26 | Gren will look for all issues closed with PRs that has one of those labels: "enhancement", "documentation" or "bug". If you noticed a missing issue in the release, make sure it has the relevant labels.
27 |
28 | [personal access token]: https://github.com/settings/tokens
29 | [gren]: https://github.com/github-tools/github-release-notes
30 | [pr-bug]: https://github.com/github-tools/github-release-notes/issues/128
31 | [issues-bug]: https://github.com/github-tools/github-release-notes/issues/209
--------------------------------------------------------------------------------
/tests/blackbox/run_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Abort script on error
4 | set -e
5 | set -x
6 |
7 | function run_tests()
8 | {
9 | dotnet test ./blackbox.csproj
10 | }
11 |
12 | if [ -z "$PROXY_URL" ]
13 | then
14 | echo PROXY_URL is not set, not running security checks
15 | run_tests
16 | else
17 | ZAP_URL=$(echo $PROXY_URL | sed -e 's/https\?:\/\///')
18 | ./wait-for-it.sh $ZAP_URL -t 300
19 | echo "ZAP is ready"
20 |
21 | curl -s --fail $PROXY_URL/JSON/core/action/newSession > /dev/null
22 | curl -s --fail $PROXY_URL/JSON/pscan/action/enableAllScanners > /dev/null
23 | curl -s --fail $PROXY_URL/JSON/core/action/clearExcludedFromProxy > /dev/null
24 |
25 | # Add the rules you wish to ignore on this line, after the ids query param.
26 | curl -s --fail $PROXY_URL/JSON/pscan/action/disableScanners/?ids=10049,10021 > /dev/null
27 |
28 | # Add the URLs you wish to ignore on this line, after the regex query param - regex supported.
29 | # curl -s --fail $PROXY_URL/JSON/core/action/excludeFromProxy/?regex=
30 |
31 | run_tests
32 |
33 | echo "waiting for ZAP to finish scanning"
34 |
35 | while [ "$(curl --fail $PROXY_URL/JSON/pscan/view/recordsToScan 2> /dev/null | jq '.recordsToScan')" != '"0"' ]; do sleep 1; done
36 |
37 | if [ "$(curl --fail $PROXY_URL/JSON/core/view/urls/?zapapiformat=JSON\&formMethod=GET\&baseurl= 2> /dev/null | jq '.urls | length' > 0)" == '"0"' ];
38 | then
39 | echo "No URL was accessed by ZAP"
40 | exit -55
41 | fi
42 |
43 | curl --fail $PROXY_URL/OTHER/core/other/jsonreport/?formMethod=GET --output /reports/report.json
44 | curl --fail $PROXY_URL/OTHER/core/other/htmlreport/?formMethod=GET --output /reports/report.html
45 |
46 | curl -s --fail $PROXY_URL/JSON/core/action/saveSession/?name=blackbox\&overwrite=true > /dev/null
47 |
48 | echo "ZAP scan completed"
49 | fi
50 |
--------------------------------------------------------------------------------
/src/encrypt-api/Program.cs:
--------------------------------------------------------------------------------
1 | using App.Metrics.Formatters.Prometheus;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.Hosting;
5 | using Serilog;
6 |
7 | namespace Kamus
8 | {
9 | public class Program
10 | {
11 | public static void Main(string[] args)
12 | {
13 | BuildWebHost(args).Run();
14 | }
15 | public static IHost BuildWebHost(string[] args) =>
16 | Host.CreateDefaultBuilder(args)
17 | .UseSerilog()
18 | .ConfigureAppConfiguration((hostContext, config) =>
19 | {
20 | var env = hostContext.HostingEnvironment;
21 | string appsettingsPath = "appsettings.json";
22 |
23 | if (env.IsDevelopment())
24 | {
25 | appsettingsPath = "appsettings.Development.json";
26 | }
27 | config.SetBasePath(env.ContentRootPath);
28 | config.AddJsonFile(appsettingsPath, optional: true, reloadOnChange: true);
29 | config.AddJsonFile("secrets/appsettings.secrets.json", optional: true);
30 | config.AddEnvironmentVariables();
31 | })
32 | .UseMetricsEndpoints(options => {
33 | options.MetricsEndpointOutputFormatter = new MetricsPrometheusTextOutputFormatter();
34 | options.MetricsTextEndpointEnabled = false;
35 | options.EnvironmentInfoEndpointEnabled = false;
36 | })
37 | .ConfigureWebHostDefaults(webBuilder =>
38 | {
39 | webBuilder.UseStartup();
40 | })
41 | .Build();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/crd-controller/crd-controller.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 | false
6 | CustomResourceDescriptorController
7 |
8 |
9 |
10 | 0.9.0.7
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 |
42 |
--------------------------------------------------------------------------------
/site/content/docs/user/known-issues.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Known Issues"
3 | menu:
4 | main:
5 | parent: "user"
6 | identifier: "known-issues"
7 | weight: 4
8 | ---
9 | # Known Issues
10 |
11 | Having problems with Kamus? This guide is covers some known problems and solutions / workarounds.
12 |
13 | It may additionally be helpful to:
14 |
15 | - check our [issue tracker]
16 | - [file an issue][file an issue] (if there isn't one already)
17 | - reach out and ask for help on the [kamus slack][kamus slack] (use the [slack invite] link)
18 |
19 | ## Contents
20 | * [Encryption failure](#encryption-failure)
21 |
22 | ## Encryption failure
23 | You might experience an issue when trying to encrypt with the CLI, similar to the following:
24 | ```
25 | [error kamus-cli]: Error while trying to encrypt with kamus: Encrypt request failed due to unexpected error. Status code: <>
26 | ```
27 |
28 | When this happens, try to check the following:
29 |
30 | * Status code 400? Might be because you tried to encrypt a secret for the default service account in a namespace? It's currently not supported by Kamus (see [deny default sa] control for more details). There is open issue ([#130]) to improve the error message.
31 | * Status code 500? Check Kamus error logs using (there is an open issue [#120] to improve the error message):
32 |
33 | ```
34 | kubectl logs -l "app=kamus,component=encryptor" --since 5m
35 | ```
36 |
37 | [issue tracker]: https://github.com/Soluto/Kamus/issues
38 | [file an issue]: https://github.com/Soluto/Kamus/issues/new
39 | [kamus slack]: http://k8s-kamus.slack.io/
40 | [slack invite]: https://join.slack.com/t/k8s-kamus/shared_invite/enQtODA2MjI3MjAzMjA1LThlODkxNTg3ZGVmMjVkOTBhY2RmMmRjOWFiOGU2NzQ1ODU4ODNiMDJiZTE5ZTY4YmRiOTM3MjI0MDc0OGFkN2E
41 | [deny default sa]: /docs/threatmodeling/controls/decryption/deny_default_sa
42 | [#130]: https://github.com/Soluto/kamus/issues/130
43 | [#120]: https://github.com/Soluto/kamus/issues/122
44 |
--------------------------------------------------------------------------------
/init-container/tests/run_test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | echo "Removing otput directory"
6 |
7 | rm -rf output
8 |
9 | echo "starting wiremock"
10 |
11 | docker-compose up -d --build wiremock
12 |
13 | docker-compose build decryptor
14 |
15 | echo "running decryptor - json format"
16 |
17 | OUTPUT_FORMAT=json docker-compose run decryptor
18 |
19 | echo "comparing out.json and expected.json files"
20 |
21 | diff -q output/out.json expected.json
22 |
23 | rm -rf output
24 |
25 | echo "running decryptor - cfg format"
26 |
27 | OUTPUT_FORMAT=cfg docker-compose run decryptor
28 |
29 | echo "comparing out.cfg and expected.cfg files"
30 |
31 | diff -q output/out.cfg expected.cfg
32 |
33 | rm -rf output
34 |
35 | echo "running decryptor - cfg strict format"
36 |
37 | OUTPUT_FORMAT=cfg-strict docker-compose run decryptor
38 |
39 | echo "comparing out.cfg-strict and expected-strict.cfg files"
40 |
41 | diff -q output/out.cfg-strict expected-strict.cfg
42 |
43 | rm -rf output
44 |
45 | echo "running decryptor - custom format"
46 |
47 | cp templates/template.ejs encrypted/
48 | OUTPUT_FORMAT=custom docker-compose run decryptor
49 | rm -rf encrypted/template.ejs
50 | echo "comparing out.custom and expected-custom.txt files"
51 |
52 | diff -q output/out.custom expected-custom.txt
53 |
54 | rm -rf output
55 |
56 | echo "running decryptor - files format"
57 |
58 | OUTPUT_FORMAT=files docker-compose run decryptor
59 |
60 | echo "comparing output directory with expected.json"
61 |
62 | for f in "output"/*; do
63 | output=$(cat "$f")
64 | if jq -e . >/dev/null 2>&1 <<<$output; then
65 | expected=$(cat expected.json | jq -cr .\"$(basename $f)\")
66 | else
67 | expected=$(cat expected.json | jq -r .$(basename $f))
68 | fi
69 | diff <(echo $output) <(echo $expected)
70 | done
71 |
72 | rm -rf output
73 |
74 | echo "running decryptor - upper case"
75 |
76 | OUTPUT_FORMAT=JSON docker-compose run decryptor
77 |
78 | if [[ $? != 0 ]]
79 | then
80 | echo "should not fail on upper case format"
81 | exit 1
82 | fi
83 |
--------------------------------------------------------------------------------
/tests/blackbox/Wiremock/mappings/apis_authenticationk8sio_v1_tokenreviews-ff46d3b4-0aad-422d-8215-e99cff8f3188.json:
--------------------------------------------------------------------------------
1 | {
2 | "id" : "ff46d3b4-0aad-422d-8215-e99cff8f3188",
3 | "name" : "apis_authenticationk8sio_v1_tokenreviews",
4 | "request" : {
5 | "url" : "/apis/authentication.k8s.io/v1/tokenreviews",
6 | "method" : "POST",
7 | "bodyPatterns" : [ {
8 | "equalToJson" : "{\n \"spec\": {\n \"token\": \"valid-token\"\n }\n}",
9 | "ignoreArrayOrder" : true,
10 | "ignoreExtraElements" : true
11 | } ]
12 | },
13 | "response" : {
14 | "status" : 201,
15 | "body" : "{\"kind\":\"TokenReview\",\"apiVersion\":\"authentication.k8s.io/v1\",\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZXYtb3BzIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tbjRtbGgiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6Ijg1OWQ5ZWQ3LTZmMGItMTFlOC1hMzBkLTA4MDAyN2MzNWVjYSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZXYtb3BzOmRlZmF1bHQifQ.F8qlI5u_NbVpUXwGUAc8ko8Xi3h38UHoKU8TPvrzf1WaJJGoyK7qIx8XnR90EjZIPAB_CKVQGYiCT3BR8TNWGvndo_XD6ktMeqjAsqagesTj2zRuP3x53psVeYnukwtxLdw0QwnJpqmx5VdljgzWSBylXzIq9pZlejziQBvFLQ2Ruzph9jWnmydPG7IO9A5XGpJev7wDqHe1KOA7zAVEVDsKlKw5P2pisRFELbmISenHCDwhrwdoixVuvEfznZcPHnHHtcZW1rtsgW-Hyq_a5O_grAgyhCXCVVMIOd7hTZ62C8w7YJQN07AFSsI_96lwZLKoHEK4EBpym4FaiKGkcA\"},\"status\":{\"authenticated\":true,\"user\":{\"username\":\"system:serviceaccount:default:default\",\"uid\":\"859d9ed7-6f0b-11e8-a30d-080027c35eca\",\"groups\":[\"system:serviceaccounts\",\"system:serviceaccounts:dummy\",\"system:authenticated\"]}}}\n",
16 | "headers" : {
17 | "Content-Type" : "application/json",
18 | "Date" : "Thu, 12 Jul 2018 05:21:59 GMT"
19 | }
20 | },
21 | "uuid" : "ff46d3b4-0aad-422d-8215-e99cff8f3188",
22 | "persistent" : true,
23 | "insertionIndex" : 3
24 | }
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/architecture.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Architecture"
3 | menu:
4 | main:
5 | parent: "Threat Modeling"
6 | identifier: "architecture"
7 | weight: 1
8 | ---
9 |
10 | # Kamus Architecture
11 | Kamus consist from 4 components:
12 |
13 | * Encrypt API - handling encryption
14 | * Decrypt API - handling decryption, should not be exposed externally
15 | * KMS - Handling the encryption using various providers.
16 | * Controller - Responsible for interavtion with Kubernetes API, currently only for CRUD operations on KamusSecrets objects.
17 |
18 | ## First flow - using the Init Container
19 |
20 | High-level overview of encryption/decryption flow:
21 |
22 |
23 | Let's take a deeper look of what's inside a pod:
24 |
25 |
26 | We have multiple objects here:
27 |
28 | * We have the pods, which run (at least) 2 containers, the application container and the init container.
29 | * The config map, contains the encrypted secrets
30 | * An [emptyDir] (using memory medium) volume, shared between the containers.
31 | * The init container, read the encrypted secrets, decrypt them and write a configuration file to the shared volume.
32 | * The application container - this is where the user code is running.
33 | The application consume the configuration file with the decrypted secrets from the shared volume.
34 | * A [service account], used by the init container to authenticate to Kamus decryptor.
35 |
36 | ## Second flow - using KamusSecret
37 |
38 | High-level overview of encryption/decryption flow:
39 |
40 |
41 | Kubernetes Icons created by [Kubernetes Community]
42 |
43 | [Kubernetes Community]: https://github.com/kubernetes/community
44 | [emptyDir]: https://kubernetes.io/docs/concepts/storage/volumes/#emptydir
45 | [service account]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
46 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/threats/decryption/pod_impersonation.md:
--------------------------------------------------------------------------------
1 | # Impersonating pod to decrypt it's secrets
2 |
3 | Feature: Impersonating pod to decrypt it's secrets
4 | In order to decrypt pod's secrets
5 | As an attacker
6 | I want to impersonate this pod
7 |
8 | Scenario Outline: Impersonation via leaked token
9 | Given a service account token was compromised
10 | When the attacker use this token for authentication
11 | Then the attacker can decrypt this pod's secrets forever
12 |
13 | Examples: Data types
14 | | data-type |
15 | | password |
16 | | API key |
17 | | X.509 private key |
18 | | SSH private key |
19 |
20 | Scenario: Service accounts mount
21 | Given a permission to create a pod
22 | When the attacker launch a pod with anther pod's service account
23 | Then the attacker can use the token for authentication and decrypt the secrets
24 |
25 | Scenario: Impersonate Kubernetes API
26 | Given a compromised Kubernetes node
27 | And the attacker can spoof requests to Kubernetes API
28 | When Kamus use the API to review tokens
29 | Then the attacker can approve all requests
30 |
31 | ## Remarks
32 |
33 | * Controls:
34 | * [Deny request for default SA](/docs/threatmodeling/controls/decryption/deny_default_sa)
35 | * [Deny Kuberentes secrets view permissions](/docs/threatmodeling/controls/decryption/deny_secret_view)
36 | * [Use TLS when accessing Kuberentes API](/docs/threatmodeling/controls/decryption/k8s_api_tls)
37 | * [Use a policy to control pod's service account](/docs/threatmodeling/controls/decryption/opa_pods_secrets)
38 | * References:
39 | * https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
40 | * https://kubernetes.io/docs/concepts/configuration/secret#service-accounts-automatically-create-and-attach-secrets-with-api-credentials
41 | * https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.12/#tokenreview-v1-authentication-k8s-io
42 |
43 | Back to [Threats and Controls](/docs/threatmodeling/threats_controls)
--------------------------------------------------------------------------------
/tests/crd-controller/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: crd-controller
5 | ---
6 | kind: ClusterRole
7 | apiVersion: rbac.authorization.k8s.io/v1
8 | metadata:
9 | namespace: default
10 | name: kamus-crd
11 | rules:
12 | - apiGroups: ["soluto.com"] # "" indicates the core API group
13 | resources: ["kamussecrets"]
14 | verbs: ["watch"]
15 | - apiGroups: [""] # "" indicates the core API group
16 | resources: ["secrets"]
17 | verbs: ["create", "delete", "patch"]
18 | ---
19 | kind: ClusterRoleBinding
20 | apiVersion: rbac.authorization.k8s.io/v1
21 | metadata:
22 | name: crd-controller
23 | subjects:
24 | - kind: ServiceAccount
25 | name: crd-controller
26 | namespace: default
27 | roleRef:
28 | kind: ClusterRole
29 | name: kamus-crd
30 | apiGroup: rbac.authorization.k8s.io
31 | ---
32 | apiVersion: v1
33 | kind: Service
34 | metadata:
35 | name: kamus-controller
36 | labels:
37 | app: kamus
38 | component: crd-controller
39 | heritage: Tiller
40 | spec:
41 | type: ClusterIP
42 | ports:
43 | - port: 443
44 | targetPort: 8888
45 | protocol: TCP
46 | name: http-kamus-controller
47 | selector:
48 | app: kamus
49 | component: crd-controller
50 | ---
51 | apiVersion: apps/v1
52 | kind: Deployment
53 | metadata:
54 | name: kamus-crd-controller
55 | labels:
56 | app: kamus
57 | component: crd-controller
58 | spec:
59 | replicas: 1
60 | selector:
61 | matchLabels:
62 | app: kamus
63 | component: crd-controller
64 | template:
65 | metadata:
66 | labels:
67 | app: kamus
68 | component: crd-controller
69 | spec:
70 | serviceAccountName: crd-controller
71 | containers:
72 | - name: controller
73 | image: crd-controller
74 | imagePullPolicy: IfNotPresent
75 | env:
76 | - name: Controller__ReconciliationIntervalInSeconds
77 | value: "10"
78 | livenessProbe:
79 | httpGet:
80 | path: /healthz
81 | port: 9999
82 | readinessProbe:
83 | httpGet:
84 | path: /healthz
85 | port: 9999
86 |
--------------------------------------------------------------------------------
/init-container/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Running the init container locally
2 |
3 | 1. Make sure you have a working Kubernetes cluster (run `kubectl get pods` to check).
4 | 2. Start by running the decryptor API - run the following in the decryptor folder (`src/decrypt-api`):
5 | ```
6 | dotnet run
7 | ```
8 | 3. Run the following command to get a valid service account token:
9 | ```
10 | kubectl get secret $(kubectl get sa default -o jsonpath='{.secrets[0].name}') -o jsonpath='{.data.token}' | base64 -D > token.txt
11 | ```
12 | 4. Set the following env var for the init container to work correctly:
13 | ```
14 | export set TOKEN_FILE_PATH=$(pwd)/token.txt
15 | export set KAMUS_URL=http://localhost:5000
16 | ```
17 | 5. Now you can run the init container:
18 | ```
19 | node index.js -e encrypted -d decrypted -n output.json
20 | ```
21 |
22 | The first argument (`-e`) point to the folder with the encrypted files, the second (`-d`) point to the folder that will contain the decrypted items and (`-n`) is the decrypted file name.
23 |
24 | And you should see a file name `output.json` created under `decrypted` folder with the decrypted content.
25 | Now you can run the init continer locally, debug it and add features.
26 |
27 | Having more questions? Something unclear? Reach out to us via [Slack](https://join.slack.com/t/k8s-kamus/shared_invite/enQtODA2MjI3MjAzMjA1LThlODkxNTg3ZGVmMjVkOTBhY2RmMmRjOWFiOGU2NzQ1ODU4ODNiMDJiZTE5ZTY4YmRiOTM3MjI0MDc0OGFkN2E) or [file an issue](https://github.com/Soluto/kamus/issues/new)
28 |
29 | ## Tests
30 | The tests are using [WireMock](http://wiremock.org/) to mock the decryptor api (you can find ore about it [here](https://www.omerlh.info/2019/02/06/wiremock-for-fun-and-mocking/)).
31 | Running the tests is simple:
32 | 1. Build the init container docker image:
33 | ```
34 | yarn run build
35 | ```
36 | 2. Set the environment variable so the tests will use the local image:
37 | ```
38 | export set INIT_CONTAINER_IMAGE=init-container
39 | ```
40 | 3. Run the tests
41 | ```
42 | yarn run test
43 | ```
44 |
45 | Repeat steps 1 & 3 each time you change the code.
46 |
47 | The tests simply take various parameters and compare the generated file from the init container with the expected files.
--------------------------------------------------------------------------------
/site/content/docs/user/quick-start.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Quick Start"
3 | menu:
4 | main:
5 | parent: "user"
6 | identifier: "quick-start"
7 | weight: 1
8 | ---
9 |
10 | # Quick Start
11 |
12 | The simple way to run Kamus is by using the Helm chart:
13 | ```
14 | helm repo add soluto https://charts.soluto.io
15 | helm upgrade --install kamus soluto/kamus
16 | ```
17 | Refer to the [installation guide](./install.md) to learn about production grade deployment.
18 | After installing Kamus, you can start using it to encrypt secrets.
19 | Kamus encrypt secrets for a specific application, represent by a [Kubernetes Service Account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account).
20 | Create a service account for your application, and mount it on the pods running your application.
21 | Now, when you know the name of the service account, and the namespace it exists in, install Kamus CLI:
22 | ```
23 | npm install -g @soluto-asurion/kamus-cli
24 | ```
25 | Use Kamus CLI to encrypt the secret:
26 | ```
27 | kamus-cli encrypt \\
28 | --secret super-secret \\
29 | --service-account kamus-example-sa \\
30 | --namespace default \\
31 | --kamus-url \\
32 | ```
33 |
34 | When Kamus URL is a url pointing to the encryptor pod of Kamus, exposed either via port forward (e.g. `kubectl port-forward svc/kamus-encryptor 9999:9999`) or via ingress.
35 | In case of non-https Kamus URL (e.g. `http://localhost:`), you'll have to add the `--allow-insecure-url` flag to enable http protocol.
36 |
37 | Pass the value returned by the CLI to your pod, and use Kamus Decrypt API to decrypt the value.
38 | The simplest way to achieve that is by using the init container.
39 | An alternative is to use Kamus decrypt API directly in the application code.
40 | To make it clearer, take a look on a working [example app](https://github.com/Soluto/kamus/tree/master/example).
41 | You can deploy this app to any Kubernetes cluster that has Kamus installed, to understand how it works.
42 |
43 | Have a question? Something is not clear? Reach out to us on [Kamus Slack](https://join.slack.com/t/k8s-kamus/shared_invite/enQtODA2MjI3MjAzMjA1LThlODkxNTg3ZGVmMjVkOTBhY2RmMmRjOWFiOGU2NzQ1ODU4ODNiMDJiZTE5ZTY4YmRiOTM3MjI0MDc0OGFkN2E)!
44 |
--------------------------------------------------------------------------------
/src/decrypt-api/Program.cs:
--------------------------------------------------------------------------------
1 | using App.Metrics.Formatters.Prometheus;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.Hosting;
5 | using Serilog;
6 |
7 | namespace Kamus
8 | {
9 | public class Program
10 | {
11 | public static void Main(string[] args)
12 | {
13 | BuildWebHost(args).Run();
14 | }
15 | public static IHost BuildWebHost(string[] args) =>
16 | Host.CreateDefaultBuilder(args)
17 | .UseSerilog()
18 | .ConfigureAppConfiguration((hostContext, config) =>
19 | {
20 | var env = hostContext.HostingEnvironment;
21 | string appsettingsPath = "appsettings.json";
22 |
23 | if (env.IsDevelopment())
24 | {
25 | appsettingsPath = "appsettings.Development.json";
26 | }
27 | config.SetBasePath(env.ContentRootPath);
28 | config.AddJsonFile(appsettingsPath, optional: true, reloadOnChange: true);
29 | config.AddJsonFile("secrets/appsettings.secrets.json", optional: true);
30 | config.AddEnvironmentVariables();
31 | })
32 | .UseMetricsEndpoints(options => {
33 | options.MetricsEndpointOutputFormatter = new MetricsPrometheusTextOutputFormatter();
34 | options.MetricsTextEndpointEnabled = false;
35 | options.EnvironmentInfoEndpointEnabled = false;
36 | })
37 | .ConfigureWebHostDefaults(webBuilder =>
38 | {
39 | webBuilder.UseStartup();
40 | })
41 | .Build();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/crd-controller/tls-KamusSecretV1Alpha2.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: "soluto.com/v1alpha2"
2 | kind: KamusSecret
3 | metadata:
4 | annotations:
5 | key: value
6 | labels:
7 | key: value
8 | name: my-tls-secret
9 | type: TlsSecret
10 | stringData:
11 | key: J9NYLzTC/O44DvlCEZ+LfQ==:Cc9O5zQzFOyxwTD5ZHseqg==
12 | data:
13 | key3: 5SRnC8HJ6gJEOCpgby3ZSQ==:LUfzADu23z1l9CWFXzR71/Ua74IxI12Ehn18d5hnqA+C40D6o1qfaaOLuPzwPYILwsViZvg7FymTB9c/1vLlwnqQJfRXNNKg07cTD5tGoufKrQrgMpOHcEKsq/k3jwQ5MSyU99npBmPFkCZ93uE1b6llSbBkzp80cBQB+Peb7WtyfG4WoOn15hy7Eb71tcdNcRLd1r5pWaPWEIlMnVB8TERCfCpTUakXmuM5mvq5PTOp/OfRGpbqG3VRf/iDHSpSOqiNKhq6eiOWvG+WTCVqjpMv4iQiMlTCcJT31ayUHxafx1bg8EX5SuMVc0sHT7wwX4BBMDUCQ8i7qXHNS5gGAl6LXHC2fhfsK/PAJVDZ18PS/ISDuzAs/iV3zffqqLCZxy5qFDyBq5hdjE2deLhH40pL1G9i7fto40ikftdqKLFaa7pxc0A88bvQihkDuPj9gtAmXt7k+hnUXlPdP5VuoKGhfJUMaEDIwTxUekzcCPDPas9XrmLWyN27BFs6Z+s/kDL+8ZGHP4yyeJyxZV0Gxf0k7RY65GyFzkvcZElq8aI8sSSc9D4Li5OilpXb0M5/s+oZj0QCwwFR2+rNTc4vm1l5s2vRmNMHZ/mQEy1qSJbEvA65U6axYoRcjQIEnvth+sZHy1E3CNBW7LRvUMf2KCy6qX+rjV/i6krAVyohAOLCfWtS+Wi3ulkefoFbKLNVLVaDVP15s4c+dqsDAzscl3HtQHRlhkOlr9hAEsX7qQlRue2S8apDp4SN8umpaTFgZy7u03yTWE64fGYyubKYRYo38D0wTOI7Z9+47m/z52rk6214A3mxoTY8Mm4bmCyxcPwGMnKXdx8x9FN56mpdFifcQLx1HffkGM/F5Hj6/xUUaquHWnWnh3keQjm1gdE0eE5YLB/EZggczj5HiahTgd3IZ/oUd8a2bGo+VToTRimOczz8je4WN3+4px2KXRs+qgaTUTUZ/m1AT90PewOKVhHfWEZKmU9063xqBjtytMyFQxZkFvQqwZNmDYmkWMrqdkC2u5/yjm1Dc1OUEy6ImnxuStCB4j+8B7gXPBg+x/nSbY8vjhbnyhmXQ37m54rFN3Jqgr0XMg5c9h2eMs2oMw/1nGFxZqzxa9VdjsG4sPcfEAECmjgnqaKVY/d7tUoXEKYQWVcoetL5zQxo4Vg9BTFaIO+npwW0Dmc5ED0ShqfKVdUkN+jx5sm16Ik6SR+xvpgh8g4YmA5GmlhLUZuSPn7jTLYmt+1e9ZpvQnNRrSW9hzmpW5FCA2i7hr83x9NmW2S7iPQ979to+i1Ak8f8B1s95bJcgkzEv9kHyd+a6kroM2AXXq0Ra2xXSNYryAS+TESYs7VPzsXeGXzmcRYIwEPqA8Kh3/8b6oNdNzY1+PHavk9Op6jTWnlsGOnRMi1WzC4nFDa9V5lvEFtBT4c1dhNnUVuecBy7yeo2LopYsY6+7rKlWFHCSfC4srZtv7CIRIO6zNAao+Cl6g9O5d+1Yu4/fI9XJMMMqJZZBUl7TGwgGTZhoWol9H5ppgShrV6nYbq2Ekyw2y0I8f96NGRrzeX9AiApoCM/qfC42NP3zEvewdgtzPlo8O0izf76R8ggc/c1rcaQwnMB1FHfPIYup10oVTJvoH2mHSVRCrCuKRQqqB/uCVIglgDjA/jnK68+yoUBD+pwOy3xiyVkQBTmxpnGURMzqXxTypP2YOXHFISnuyTElr7JRNoaK7Oep2hhiXO+jlKEXHe3jeWOKlOW8gpPKKd2rxBrrX+IeaJx7EWKdxkCbackd7cpaeRLVQV8WCUIb91HeMUqz1DEALky+lsfXOr5VwaiikRcJzrlwJkIarNSoM2644Qf5Gyo87z3kmnHyHTqKerC5VrRSgEcH2Z3Ag4Skdf5qMnYC3jm90jp2hhZzq4pMcgGhambiUb1
14 | serviceAccount: some-sa
--------------------------------------------------------------------------------
/tests/crd-controller/tls-KamusSecretV1Alpha2-with-annotations.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: "soluto.com/v1alpha2"
2 | kind: KamusSecret
3 | metadata:
4 | annotations:
5 | key: value
6 | labels:
7 | key: value
8 | name: my-tls-secret
9 | type: TlsSecret
10 | stringData:
11 | key: J9NYLzTC/O44DvlCEZ+LfQ==:Cc9O5zQzFOyxwTD5ZHseqg==
12 | data:
13 | key3: 5SRnC8HJ6gJEOCpgby3ZSQ==:LUfzADu23z1l9CWFXzR71/Ua74IxI12Ehn18d5hnqA+C40D6o1qfaaOLuPzwPYILwsViZvg7FymTB9c/1vLlwnqQJfRXNNKg07cTD5tGoufKrQrgMpOHcEKsq/k3jwQ5MSyU99npBmPFkCZ93uE1b6llSbBkzp80cBQB+Peb7WtyfG4WoOn15hy7Eb71tcdNcRLd1r5pWaPWEIlMnVB8TERCfCpTUakXmuM5mvq5PTOp/OfRGpbqG3VRf/iDHSpSOqiNKhq6eiOWvG+WTCVqjpMv4iQiMlTCcJT31ayUHxafx1bg8EX5SuMVc0sHT7wwX4BBMDUCQ8i7qXHNS5gGAl6LXHC2fhfsK/PAJVDZ18PS/ISDuzAs/iV3zffqqLCZxy5qFDyBq5hdjE2deLhH40pL1G9i7fto40ikftdqKLFaa7pxc0A88bvQihkDuPj9gtAmXt7k+hnUXlPdP5VuoKGhfJUMaEDIwTxUekzcCPDPas9XrmLWyN27BFs6Z+s/kDL+8ZGHP4yyeJyxZV0Gxf0k7RY65GyFzkvcZElq8aI8sSSc9D4Li5OilpXb0M5/s+oZj0QCwwFR2+rNTc4vm1l5s2vRmNMHZ/mQEy1qSJbEvA65U6axYoRcjQIEnvth+sZHy1E3CNBW7LRvUMf2KCy6qX+rjV/i6krAVyohAOLCfWtS+Wi3ulkefoFbKLNVLVaDVP15s4c+dqsDAzscl3HtQHRlhkOlr9hAEsX7qQlRue2S8apDp4SN8umpaTFgZy7u03yTWE64fGYyubKYRYo38D0wTOI7Z9+47m/z52rk6214A3mxoTY8Mm4bmCyxcPwGMnKXdx8x9FN56mpdFifcQLx1HffkGM/F5Hj6/xUUaquHWnWnh3keQjm1gdE0eE5YLB/EZggczj5HiahTgd3IZ/oUd8a2bGo+VToTRimOczz8je4WN3+4px2KXRs+qgaTUTUZ/m1AT90PewOKVhHfWEZKmU9063xqBjtytMyFQxZkFvQqwZNmDYmkWMrqdkC2u5/yjm1Dc1OUEy6ImnxuStCB4j+8B7gXPBg+x/nSbY8vjhbnyhmXQ37m54rFN3Jqgr0XMg5c9h2eMs2oMw/1nGFxZqzxa9VdjsG4sPcfEAECmjgnqaKVY/d7tUoXEKYQWVcoetL5zQxo4Vg9BTFaIO+npwW0Dmc5ED0ShqfKVdUkN+jx5sm16Ik6SR+xvpgh8g4YmA5GmlhLUZuSPn7jTLYmt+1e9ZpvQnNRrSW9hzmpW5FCA2i7hr83x9NmW2S7iPQ979to+i1Ak8f8B1s95bJcgkzEv9kHyd+a6kroM2AXXq0Ra2xXSNYryAS+TESYs7VPzsXeGXzmcRYIwEPqA8Kh3/8b6oNdNzY1+PHavk9Op6jTWnlsGOnRMi1WzC4nFDa9V5lvEFtBT4c1dhNnUVuecBy7yeo2LopYsY6+7rKlWFHCSfC4srZtv7CIRIO6zNAao+Cl6g9O5d+1Yu4/fI9XJMMMqJZZBUl7TGwgGTZhoWol9H5ppgShrV6nYbq2Ekyw2y0I8f96NGRrzeX9AiApoCM/qfC42NP3zEvewdgtzPlo8O0izf76R8ggc/c1rcaQwnMB1FHfPIYup10oVTJvoH2mHSVRCrCuKRQqqB/uCVIglgDjA/jnK68+yoUBD+pwOy3xiyVkQBTmxpnGURMzqXxTypP2YOXHFISnuyTElr7JRNoaK7Oep2hhiXO+jlKEXHe3jeWOKlOW8gpPKKd2rxBrrX+IeaJx7EWKdxkCbackd7cpaeRLVQV8WCUIb91HeMUqz1DEALky+lsfXOr5VwaiikRcJzrlwJkIarNSoM2644Qf5Gyo87z3kmnHyHTqKerC5VrRSgEcH2Z3Ag4Skdf5qMnYC3jm90jp2hhZzq4pMcgGhambiUb1
14 | serviceAccount: some-sa
15 | propagateAnnotations: true
--------------------------------------------------------------------------------
/src/key-managment/SymmetricKeyManagement.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Threading.Tasks;
4 | using Org.BouncyCastle.Crypto.Digests;
5 | using Org.BouncyCastle.Crypto.Generators;
6 | using Org.BouncyCastle.Crypto.Parameters;
7 |
8 | namespace Kamus.KeyManagement
9 | {
10 | public class SymmetricKeyManagement : IKeyManagement
11 | {
12 | private readonly byte[] mKey;
13 | private readonly bool mUseKeyDerivation;
14 | private const int derivedKeySize = 32;
15 |
16 | public SymmetricKeyManagement(byte[] key, bool useKeyDerivation)
17 | {
18 | mKey = key;
19 | mUseKeyDerivation = useKeyDerivation;
20 | }
21 |
22 | public Task Decrypt(string encryptedData, string serviceAccountId)
23 | {
24 | var splitted = encryptedData.Split(':');
25 | if (splitted.Length != 2) {
26 | throw new InvalidOperationException("Encrypted data format is invalid");
27 | }
28 |
29 | var iv = Convert.FromBase64String(splitted[0]);
30 | var data = Convert.FromBase64String(splitted[1]);
31 |
32 | var key = mUseKeyDerivation ? DeriveKey(serviceAccountId) : mKey;
33 |
34 | var result = RijndaelUtils.Decrypt(key, iv, data);
35 |
36 | return Task.FromResult(Encoding.UTF8.GetString(result));
37 | }
38 |
39 | public Task Encrypt(string data, string serviceAccountId, bool createKeyIfMissing = true)
40 | {
41 | var key = mUseKeyDerivation ? DeriveKey(serviceAccountId) : mKey;
42 |
43 | var (decryptedData, iv) = RijndaelUtils.Encrypt(key, Encoding.UTF8.GetBytes(data));
44 |
45 | return Task.FromResult(Convert.ToBase64String(iv) + ":" + Convert.ToBase64String(decryptedData));
46 | }
47 |
48 | private byte[] DeriveKey(string serviceAccountId)
49 | {
50 | var generator = new HkdfBytesGenerator(new Sha256Digest());
51 |
52 | generator.Init(HkdfParameters.SkipExtractParameters(mKey, Encoding.UTF8.GetBytes(serviceAccountId)));
53 |
54 | var derivedKey = new byte[derivedKeySize];
55 |
56 | generator.GenerateBytes(derivedKey, 0, derivedKeySize);
57 |
58 | return derivedKey;
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/site/assets/js/inline.js:
--------------------------------------------------------------------------------
1 | /* used when sidebar is manually toggled */
2 | function toggleSidebar() {
3 | if (document.body.classList.contains('sidebar-collapsed')) {
4 | window.localStorage.setItem('sidebar-collapsed', 'false');
5 | showSideBar();
6 | } else {
7 | window.localStorage.setItem('sidebar-collapsed', 'true');
8 | hideSideBar();
9 | }
10 | }
11 |
12 | function showSideBar() {
13 | document.body.classList.remove('sidebar-collapsed');
14 | }
15 |
16 | function hideSideBar() {
17 | document.body.classList.add('sidebar-collapsed');
18 | }
19 |
20 | /* get the page width */
21 | function getWidth() {
22 | return Math.max(
23 | document.body.scrollWidth,
24 | document.documentElement.scrollWidth,
25 | document.body.offsetWidth,
26 | document.documentElement.offsetWidth,
27 | document.documentElement.clientWidth
28 | );
29 | }
30 |
31 | var oldWidth;
32 | document.addEventListener("DOMContentLoaded", function () {
33 | // note: the default state of the page on load is collapsed
34 | var manualCollapsed = window.localStorage.getItem('sidebar-collapsed');
35 | var width = getWidth();
36 | // if we're now under 900px don't unhide it
37 | // otherwise only unhide if the user has not yet manually toggled it
38 | // or has toggled it back to visible
39 | if (width > 900 && (manualCollapsed == 'false' || manualCollapsed == null)) {
40 | showSideBar();
41 | }
42 | // setup listener for width change
43 | oldWidth = width;
44 | window.addEventListener("resize", function () {
45 | // bail out early if it wasn't the width that changed
46 | var width = getWidth();
47 | if (oldWidth == width) {
48 | oldWidth = width;
49 | return;
50 | }
51 | oldWidth = width;
52 | // if we're now under 900px, hide it
53 | if (width <= 900) {
54 | hideSideBar();
55 | } else {
56 | // otherwise only unhide if the user has not yet manually toggled it
57 | // or has toggled it back to visible
58 | var manualCollapsed = window.localStorage.getItem('sidebar-collapsed');
59 | if (manualCollapsed == 'false' || manualCollapsed == null) {
60 | showSideBar();
61 | }
62 | }
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/src/key-managment/EnvelopeEncryptionDecorator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Text.RegularExpressions;
4 | using System.Threading.Tasks;
5 | using Serilog;
6 |
7 | namespace Kamus.KeyManagement
8 | {
9 | public class EnvelopeEncryptionDecorator : IKeyManagement
10 | {
11 | private readonly IKeyManagement mMasterKeyManagement;
12 | private readonly int mMaximumDataLength;
13 | private readonly ILogger mLogger = Log.ForContext();
14 |
15 | public EnvelopeEncryptionDecorator(IKeyManagement masterKeyManagement, int maximumDataLength)
16 | {
17 | mMasterKeyManagement = masterKeyManagement;
18 | mMaximumDataLength = maximumDataLength;
19 | }
20 |
21 | public async Task Encrypt(string data, string serviceAccountId, bool createKeyIfMissing = true)
22 | {
23 | if (data.Length <= mMaximumDataLength)
24 | {
25 | return await mMasterKeyManagement.Encrypt(data, serviceAccountId, createKeyIfMissing);
26 | }
27 |
28 | mLogger.Information("Encryption data too length, using envelope encryption");
29 |
30 | var dataKey = RijndaelUtils.GenerateKey(256);
31 | var (encryptedData, iv) = RijndaelUtils.Encrypt(dataKey, Encoding.UTF8.GetBytes(data));
32 | var encryptedDataKey = await mMasterKeyManagement.Encrypt(Convert.ToBase64String(dataKey), serviceAccountId, createKeyIfMissing);
33 | return EnvelopeEncryptionUtils.Wrap(encryptedDataKey, iv, encryptedData);
34 |
35 | }
36 |
37 | public Task Decrypt(string encryptedData, string serviceAccountId)
38 | {
39 | return EnvelopeEncryptionUtils.Unwrap(encryptedData).Match(
40 | some: async t =>
41 | {
42 | (string encryptedDataKey, byte[] iv, byte[] actualEncryptedData) = t;
43 |
44 | var key = await mMasterKeyManagement.Decrypt(encryptedDataKey, serviceAccountId);
45 |
46 | var decrypted = RijndaelUtils.Decrypt(Convert.FromBase64String(key), iv, actualEncryptedData);
47 | return Encoding.UTF8.GetString(decrypted);
48 | },
49 | none: () => mMasterKeyManagement.Decrypt(encryptedData, serviceAccountId)
50 | );
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/cli/lib/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const pjson = require('../package.json');
4 | const prog = require('caporal');
5 | const encrypt = require('./actions/encrypt');
6 | const regexGuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7 |
8 | const { ColorfulChalkLogger, INFO } = require('colorful-chalk-logger');
9 |
10 | const isVerboseLogging = (args) => args.indexOf('--verbose') > -1 || args.indexOf('-v') > -1;
11 |
12 | const logger = new ColorfulChalkLogger('kamus-cli', {
13 | level: INFO,
14 | date: false,
15 | colorful: true,
16 | }, isVerboseLogging(process.argv) ? process.argv.concat(['--log-level', 'debug']) : process.argv); // translate to ColorfulChalkLogger log level
17 |
18 | process.argv = process.argv.filter(x => x != '--verbose' && x != '-v' ); // ColorfulChalkLogger got the verbose, don't pass it to caporal
19 |
20 | prog
21 | .logger(logger)
22 | .version(pjson.version)
23 | .command('encrypt', 'Encrypt data')
24 | .action(encrypt)
25 | .option('-s, --secret ','Secret to encrypt', prog.STRING)
26 | .option('-f, --secret-file ','File with secret to encrypt', prog.STRING)
27 | .option('-a, --service-account ', 'Deployment service account', prog.REQUIRED)
28 | .option('-n, --namespace ', 'Deployment namespace', prog.REQUIRED)
29 | .option('-u, --kamus-url ', 'Kamus URL', prog.REQUIRED)
30 | .option('--auth-tenant ', 'Azure tenant id', regexGuid)
31 | .option('--auth-application ', 'Azure application id', regexGuid)
32 | .option('--auth-resource ', 'Azure resource name', prog.STRING)
33 | .option('--allow-insecure-url', 'Allow insecure (http) Kamus URL', prog.BOOL)
34 | .option('--cert-fingerprint ', 'Force server certificate to match the given fingerprint', prog.STRING)
35 | .option('--secret-file-encoding ', 'Encoding of secret file', prog.STRING)
36 | .option('-o, --output ', 'Output to file', prog.STRING)
37 | .option('-O, --overwrite', 'Overwrites file if it already exists', prog.BOOL)
38 | .option('--log-flag ', 'log format', prog.STRING)
39 | .option('--log-output ', 'output log to file', prog.STRING)
40 | .option('--log-encoding ', 'log file encoding')
41 | .option('-i, --ignore-new-line', 'ignore new line in provided file', prog.BOOL);
42 |
43 |
44 | prog.parse(process.argv);
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Kamus Example
2 | A small example app, showing the power of Kamus.
3 | Before running this demo, make sure Kamus is up and running under the namespace default, and the CLI is installed.
4 | The example show 2 deployments of the same application:
5 | * Using regular secrets (`deployment-secret`)
6 | * Using Kamus init container (`deployment-kamus`)
7 |
8 | We included both options to make it easier to understand how Kamus make it easier to consume secrets.
9 |
10 | ## Running the demo
11 | Before running the demo, make sure to install Kamus on the cluster under the namespace default.
12 | If Kamus is not installed, use the following command:
13 | ```
14 | helm repo add soluto https://charts.soluto.io
15 | helm upgrade --install soluto/kamus
16 | ```
17 |
18 | Start by encrypting a secret using the CLI:
19 | ```bash
20 | kamus-cli encrypt \
21 | --secret super-secret \
22 | --service-account kamus-example-sa \
23 | --namespace default \
24 | --kamus-url
25 | ```
26 | You might have to pass aditional arguments, based on your installation.
27 |
28 | After encrypting the secret, open `deployment-kamus\configmap.yaml`.
29 | Modify the value of `key` to the encrypted value returned from the CLI.
30 |
31 | Now, run
32 | ```
33 | kubectl apply -f deployment-kamus/
34 | ```
35 | To deploy the example app.
36 | Check deployment status using
37 | ```
38 | kubectl get pods
39 | ```
40 | Notice the `kamus-example` pods. Now run:
41 | ```
42 | kubectl port-forward deployment/kamus-example 8080:80
43 | ```
44 | Open [`http://localhost:8080`](http://localhost:8080) on your browser, you should see the encrypted secrets decrypted!
45 |
46 | In case you have issues running the demo, we made a [recorded version](https://www.youtube.com/watch?v=i_vdtubTrso&feature=youtu.be) of the demo.
47 |
48 | ## Kubernetes Secrets
49 | To complete the example, reffer to `deployment-secret`.
50 | This example shows the alternative to Kamus - using Kubernetes native secrets.
51 | Run the demo using
52 | ```
53 | kubectl apply -f deployment-kamus/
54 | ```
55 | Notice the `kamus-example` pods. Wait for the pod to be in `Completed` state, and check the logs using
56 | ```
57 | kubectl logs -l app=kamus-example
58 | ```
59 | You should see the following output
60 | ```
61 | {"key":"super-secret"}
62 | ```
63 | Editing the secrets:
64 | * Open `secret.yaml`
65 | * Decode the value under `config.json` using base64 decoder
66 | * Edit the JSON
67 | * Encode the JSON using base64 encoder, and put this value under `config.json`
68 |
--------------------------------------------------------------------------------
/src/encrypt-api/Controllers/EncryptController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Threading.Tasks;
4 | using System.Linq;
5 | using Kamus.Models;
6 | using Microsoft.AspNetCore.Authorization;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.Rest;
9 | using Kamus.Extensions;
10 | using Kamus.KeyManagement;
11 | using Serilog;
12 | using System.Net.Http;
13 |
14 | namespace Kamus.Controllers
15 | {
16 |
17 | public class EncryptController : Controller
18 | {
19 | private readonly IKeyManagement mKeyManagement;
20 | private readonly ILogger mAuditLogger = Log.ForContext().AsAudit();
21 | private readonly ILogger mLogger = Log.ForContext();
22 |
23 | public EncryptController(IKeyManagement keyManagement)
24 | {
25 | mKeyManagement = keyManagement;
26 | }
27 |
28 | [HttpPost]
29 | [Route("api/v1/encrypt")]
30 | public async Task Encrypt([FromBody]EncryptRequest body)
31 | {
32 | if (!ModelState.IsValid)
33 | {
34 | mAuditLogger.Warning("Bad request to Encrypt API: {sourceIP} {validationState}",
35 | Request.HttpContext.Connection.RemoteIpAddress,
36 | ModelState.ValidationState);
37 | return BadRequest("One or more of the required fields doesn't present in the request body.");
38 | }
39 |
40 | mAuditLogger.Information("Encryption request started, SourceIP: {sourceIp}, ServiceAccount: {sa}, Namespace: {namespace}",
41 | Request.HttpContext.Connection.RemoteIpAddress,
42 | body.ServiceAccountName,
43 | body.NamespaceName);
44 |
45 | var encryptedData = await mKeyManagement.Encrypt(body.Data, $"{body.NamespaceName}:{body.ServiceAccountName}");
46 |
47 | if (body.ServiceAccountName == "default")
48 | {
49 | return BadRequest("You cannot encrypt a secret for the default service account");
50 | }
51 |
52 | mAuditLogger.Information("Encryption request succeeded, SourceIP: {sourceIp}, ServiceAccount: {serviceAccount}, Namesacpe: {namespace}",
53 | Request.HttpContext.Connection.RemoteIpAddress,
54 | body.ServiceAccountName,
55 | body.NamespaceName);
56 |
57 | return Content(encryptedData);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/site/content/docs/threatmodeling/threats_controls.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Threats and Controls"
3 | menu:
4 | main:
5 | parent: "Threat Modeling"
6 | identifier: "threat_controls"
7 | weight: 2
8 | ---
9 |
10 | # Threats and Controls
11 | This page document the output of Kamus threat model, by listing all the different threats and mitigations that we discussed.
12 |
13 | ## Threats
14 | ### Decryption
15 |
16 | * [Impersonating pod to decrypt it's secrets](/docs/threatmodeling/threats/decryption/pod_impersonation)
17 | * [Sniff requests and responses to Kamus](/docs/threatmodeling/threats/decryption/sniffing_tampering)
18 | * [Using KamusSecret to decrypt secrets](/docs/threatmodeling/threats/decryption/leveraging_crd)
19 |
20 | ### Encryption
21 | * [Kamus server disrupting](/docs/threatmodeling/threats/encryption/denial_of_service)
22 | * [Exposing names of namespaces and service accounts](/docs/threatmodeling/threats/encryption/namespace_enumeration)
23 | * [Sniffing user's traffic](/docs/threatmodeling/threats/encryption/sniffing_user_traffic)
24 |
25 | ### KMS
26 | * [Accessing KMS with leaked credentials](/docs/threatmodeling/threats/kms/leaked_credentials)
27 | * [Breaking encryption key](/docs/threatmodeling/threats/kms/quantom_computing)
28 |
29 | ## Controls
30 | ### Decryption
31 |
32 | * [Deny default service account](/docs/threatmodeling/controls/decryption/deny_default_sa)
33 | * [Deny Kuberentes secrets view permissions](/docs/threatmodeling/controls/decryption/deny_secret_view)
34 | * [Use TLS when accessing Kuberentes API](/docs/threatmodeling/controls/decryption/k8s_api_tls)
35 | * [Serve Kamus API over TLS](/docs/threatmodeling/controls/decryption/kamus_in_cluster_tls)
36 | * [Use a policy to control pod's service account](/docs/threatmodeling/controls/decryption/opa_pods_secrets)
37 |
38 | ### Encryption
39 |
40 | * [Block anonymous internet access to Kamus](/docs/threatmodeling/controls/encryption/block_internet_access)
41 | * [Certificate pinning](/docs/threatmodeling/controls/encryption/certificate_pinning)
42 | * [Client-side encryption](/docs/threatmodeling/controls/encryption/client_side_encryption)
43 | * [Deny request for default SA](/docs/threatmodeling/controls/encryption/deny_default_sa)
44 | * [Throttling requests by source IP](/docs/threatmodeling/controls/encryption/ip_throttling)
45 |
46 | ### Key Management System
47 | * [Enable firewall protection for KMS](/docs/threatmodeling/controls/kms/firewall_protection)
48 | * [Credentials Hardening](/docs/threatmodeling/controls/kms/hardening_credentials)
49 | * [Key names obfuscation](/docs/threatmodeling/controls/kms/obfuscate_key_names)
50 | * [Use KMS with HSM support](/docs/threatmodeling/controls/kms/use_hsm)
--------------------------------------------------------------------------------
/src/key-managment/RijndaelUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Security.Cryptography;
4 |
5 | namespace Kamus.KeyManagement
6 | {
7 | public static class RijndaelUtils
8 | {
9 | public static byte[] GenerateKey(int keySize = 256)
10 | {
11 | using (var aes = new AesManaged())
12 | {
13 | aes.KeySize = 256;
14 | aes.GenerateKey();
15 | return aes.Key;
16 | }
17 | }
18 |
19 | public static (byte[] encryptedData, byte[] iv) Encrypt(byte[] key, byte[] data)
20 | {
21 | byte[] iv = GetRandomData(16);
22 | byte[] result;
23 | using (var rijndael = new RijndaelManaged())
24 | {
25 | using (var encryptor = rijndael.CreateEncryptor(key, iv))
26 | using (var resultStream = new MemoryStream())
27 | {
28 | using (var rijndaelStream = new CryptoStream(resultStream, encryptor, CryptoStreamMode.Write))
29 | using (var plainStream = new MemoryStream(data))
30 | {
31 | plainStream.CopyTo(rijndaelStream);
32 | }
33 |
34 | result = resultStream.ToArray();
35 | }
36 | }
37 |
38 | return (result, iv);
39 | }
40 |
41 | public static byte[] Decrypt(byte[] key, byte[] iv, byte[] encryptedData)
42 | {
43 | byte[] result;
44 | try
45 | {
46 | using (var rijndael = new RijndaelManaged())
47 | {
48 | using (var decryptor = rijndael.CreateDecryptor(key, iv))
49 | using (var resultStream = new MemoryStream())
50 | {
51 | using (var rijndaelStream = new CryptoStream(resultStream, decryptor, CryptoStreamMode.Write))
52 | using (var cipherStream = new MemoryStream(encryptedData))
53 | {
54 | cipherStream.CopyTo(rijndaelStream);
55 | }
56 |
57 | result = resultStream.ToArray();
58 | }
59 | }
60 | }catch(ArgumentException e)
61 | {
62 | throw new Exception($"on no {key.Length}", e);
63 | }
64 |
65 | return result;
66 |
67 | }
68 |
69 | private static byte[] GetRandomData(int size)
70 | {
71 | var provider = new RNGCryptoServiceProvider();
72 | var byteArray = new byte[size];
73 | provider.GetBytes(byteArray);
74 | return byteArray;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/encrypt-api/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using App.Metrics;
4 | using Kamus.KeyManagement;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Hosting;
7 | using Microsoft.AspNetCore.Http;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.Extensions.Configuration;
10 | using Microsoft.Extensions.DependencyInjection;
11 | using Microsoft.Extensions.Hosting;
12 | using Microsoft.OpenApi.Models;
13 | using Serilog;
14 |
15 | namespace Kamus
16 | {
17 | public class Startup {
18 |
19 | public Startup(IWebHostEnvironment env, IConfiguration configuration)
20 | {
21 | Configuration = configuration;
22 | var version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
23 | Console.WriteLine($"Kamus Encryptor API {version} starting");
24 | }
25 |
26 |
27 | public IConfiguration Configuration;
28 |
29 | // This method gets called by the runtime. Use this method to add services to the container.
30 | public void ConfigureServices (IServiceCollection services) {
31 |
32 | services.AddControllers().AddNewtonsoftJson();
33 |
34 | services.AddSwaggerGen (swagger => {
35 | swagger.SwaggerDoc("v1", new OpenApiInfo { Title = "Kamus Encryptor API", Version = "v1" });
36 | });
37 |
38 | services.AddKeyManagement(Configuration, Log.Logger);
39 |
40 | services.AddSingleton(Configuration);
41 |
42 | services.AddSingleton();
43 | }
44 |
45 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
46 | public void Configure (IApplicationBuilder app, IWebHostEnvironment env)
47 | {
48 | app.UseMetricsAllMiddleware();
49 |
50 | if (env.IsDevelopment())
51 | {
52 | app.UseDeveloperExceptionPage();
53 | }
54 | else
55 | {
56 | app.UseExceptionMiddleware();
57 | }
58 |
59 | Log.Logger = new LoggerConfiguration ()
60 | .ReadFrom.Configuration (Configuration)
61 | .CreateLogger ();
62 |
63 |
64 | app.UseRouting();
65 | app.UseMetricsErrorTrackingMiddleware();
66 |
67 | app.UseSwagger();
68 |
69 | app.UseSwaggerUI(c => {
70 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "Kamus Encryptor API");
71 | });
72 |
73 | app.UseEndpoints(endpoints =>
74 | {
75 | endpoints.MapControllers();
76 | });
77 |
78 | app.UseAuthentication();
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/tests/integration/AwsKeyManagementTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text.RegularExpressions;
4 | using System.Threading.Tasks;
5 | using Amazon;
6 | using Amazon.KeyManagementService;
7 | using Amazon.KeyManagementService.Model;
8 | using Kamus.KeyManagement;
9 | using Microsoft.Extensions.Configuration;
10 | using Xunit;
11 |
12 | namespace integration
13 | {
14 | public class AwsKeyManagementTests
15 | {
16 | private readonly IKeyManagement mAwsKeyManagement;
17 | private readonly IConfiguration mConfiguration;
18 |
19 | public AwsKeyManagementTests()
20 | {
21 | mConfiguration = new ConfigurationBuilder()
22 | .AddJsonFile("settings.json")
23 | .AddEnvironmentVariables().Build();
24 |
25 | var awsKey = mConfiguration.GetValue("KeyManagement:AwsKms:Key");
26 | var awsSecret = mConfiguration.GetValue("KeyManagement:AwsKms:Secret");
27 |
28 | var kmsService = new AmazonKeyManagementServiceClient(awsKey, awsSecret, RegionEndpoint.USEast1);
29 |
30 | mAwsKeyManagement = new AwsKeyManagement(kmsService,"", true);
31 | }
32 |
33 | [Fact]
34 | public async Task TestFullFlow()
35 | {
36 | var sa = "sa:namespace";
37 | var data = "data";
38 | var encrypted = await mAwsKeyManagement.Encrypt(data, sa);
39 | var decrypted = await mAwsKeyManagement.Decrypt(encrypted, sa);
40 |
41 | Assert.Equal(data, decrypted);
42 | }
43 |
44 | [Fact]
45 | public async Task RegressionTest()
46 | {
47 | var sa = "sa:namespace";
48 | var data = "data";
49 | var encrypted = "env$AQIDAHizI6sed7zuuIsC3swHqi0UTTDv7X15xoyC5QG9deKqMwHEoOhAcGhmWHDZ0naCQQ6lAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMGK6qLAGscq77QeC7AgEQgDuxpshMWysHf2mXmCDlFCdOKjFiGIIJvYNdJIuZCOfYZGXokLN77e+OS/ecob+SnCRRYYPMwGGWNBilYg==$o3t8Q+fpxvM+BuDID3ssqw==:2sutg2A6bmpDctVXaqDl4A==";
50 | var decrypted = await mAwsKeyManagement.Decrypt(encrypted, sa);
51 |
52 | Assert.Equal(data, decrypted);
53 | }
54 |
55 | [Fact]
56 | public async Task DecryptWithDifferentSAFails()
57 | {
58 | var sa = "sa:namespace";
59 | var sa2 = "sa2:namespace";
60 | var data = "data";
61 | var encrypted = await mAwsKeyManagement.Encrypt(data, sa);
62 |
63 | // To make sure the key does exist
64 | await mAwsKeyManagement.Encrypt(data, sa2);
65 | // ===============================
66 | await Assert.ThrowsAsync(async () => await mAwsKeyManagement.Decrypt(encrypted, "SA2:namespace"));
67 | }
68 | }
69 |
70 | public class Configuration
71 | {
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/decrypt-api/KubernetesAuthentication/KubernetesAuthenticationHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Security.Claims;
4 | using System.Text.Encodings.Web;
5 | using System.Threading.Tasks;
6 | using k8s;
7 | using k8s.Models;
8 | using Microsoft.AspNetCore.Authentication;
9 | using Microsoft.Extensions.Logging;
10 | using Microsoft.Extensions.Options;
11 |
12 | namespace Kamus.KubernetesAuthentication
13 | {
14 | public class KubernetesAuthenticationHandler : AuthenticationHandler
15 | {
16 | private readonly IKubernetes mKubernetes;
17 |
18 | public KubernetesAuthenticationHandler(
19 | IOptionsMonitor options,
20 | ILoggerFactory logger,
21 | UrlEncoder encoder,
22 | ISystemClock clock,
23 | IKubernetes kubernetes) : base(options, logger, encoder, clock)
24 | {
25 | mKubernetes = kubernetes;
26 | }
27 |
28 | protected override async Task HandleAuthenticateAsync()
29 | {
30 | string token = "";
31 | string authorization = Request.Headers["Authorization"];
32 |
33 | // If no authorization header found, nothing to process further
34 | if (string.IsNullOrEmpty(authorization))
35 | {
36 | return AuthenticateResult.NoResult();
37 | }
38 |
39 | if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
40 | {
41 | token = authorization.Substring("Bearer ".Length).Trim();
42 | }
43 |
44 | // If no token found, no further work possible
45 | if (string.IsNullOrEmpty(token))
46 | {
47 | return AuthenticateResult.NoResult();
48 | }
49 |
50 | var reviewResult = await mKubernetes.CreateTokenReviewAsync(new V1TokenReview
51 | {
52 | Spec = new V1TokenReviewSpec
53 | {
54 | Token = token
55 | }
56 | });
57 |
58 | if (!reviewResult.Status.Authenticated.HasValue || !reviewResult.Status.Authenticated.Value) {
59 | //todo: improve logging
60 | return AuthenticateResult.Fail(reviewResult.Status.Error);
61 | }
62 |
63 |
64 | var claims = new List {
65 | new Claim("sub", reviewResult.Status.User.Uid),
66 | new Claim("name", reviewResult.Status.User.Username),
67 | new Claim("Groups", string.Join(",", reviewResult.Status.User.Groups))
68 | };
69 |
70 |
71 | return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new List { new ClaimsIdentity(claims, "kubernetes") }), "kubernetes"));
72 |
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/site/content/docs/user/crd.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Creating Kubernetes Secrets"
3 | menu:
4 | main:
5 | parent: "user"
6 | identifier: "crd"
7 | weight: 3
8 | ---
9 |
10 | # Custom Resource Descriptor Support
11 | Kamus supports also creating native Kubernetes secrets from an object called KamusSecret.
12 | KamusSecret is very similar to Secret, with one small difference - all the items in it are encrypted.
13 | Using KamusSecret allows to use Kamus with applications that requires native Kubernetes secrets - for example, TLS secrets.
14 |
15 | ## Usage
16 | KamusSecret works very similary to regular secret encryption flow with Kamus.
17 | The encrypted data is represented in a format that is identical to regular [Kubernetes Secrets].
18 | Kamus will create an identical secret with the decrypted content.
19 |
20 | To encrypt the data, start by deciding to which namespace and which service account you're encrypting it.
21 | The service account does not have to exist or used by the pod consuming the secret.
22 | It just used for expressing who can consume this encrypted secret.
23 | Use the [CLI](https://github.com/Soluto/kamus/blob/master/cli/README.md) to encrypt the data:
24 | ```
25 | kamus-cli encrypt
26 | --secret super-secret \\
27 | --service-account kamus-example-sa \\
28 | --namespace default \\
29 | --kamus-url
30 | ```
31 | Now that you have the data encrypted, create a KamusSecret object, using the following manifest:
32 | ```
33 | apiVersion: "soluto.com/v1alpha2"
34 | kind: KamusSecret
35 | metadata:
36 | name: my-tls-secret //This will be the name of the secret
37 | namespace: default //The secret and KamusSecret live in this namespace
38 | type: TlsSecret //The type of the secret that will be created
39 | stringData: //Put here all the encrypted data, that will be stored (decrypted) on the secret data
40 | key: J9NYLzTC/O44DvlCEZ+LfQ==:Cc9O5zQzFOyxwTD5ZHseqg==
41 | data: //Put here base64 encoded data (usually, binary data like private keys in der format) encrypted (e.g. encrypt the value after base64 encoding it)
42 | key2: J9NYLzTC/O44DvlCEZ+LfQ==:Cc9O5zQzFOyxwTD5ZHseqg==
43 | serviceAccount: some-sa //The service account used for encrypting the data
44 | ```
45 | And finally, create the KamusSecret using:
46 | ```
47 | kubectl apply -f kamussecret.yaml
48 | ```
49 | And after a few seconds you'll see the new secret created:
50 | ```
51 | $ kubectl get secrets
52 | NAME TYPE DATA AGE
53 | default-token-m6whl kubernetes.io/service-account-token 3 1d
54 | my-tls-secret TlsSecret 1 5s
55 | ```
56 |
57 | ## Known limitation
58 | This is the alpha release of this feature, so not all functionality is supported.
59 | The current known issues:
60 |
61 | * There is no validation - so if you forgot to add mandatory keys to the KamusSecret objects, it will not be created properly.
62 |
63 | [kubernetes secrets]: (https://kubernetes.io/docs/concepts/configuration/secret/)
64 |
--------------------------------------------------------------------------------
/tests/crd-controller/run-tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -o errexit
4 | set -o nounset
5 | set -o pipefail
6 |
7 | readonly KIND_VERSION=0.10.0
8 | readonly CLUSTER_NAME=e2e-test
9 |
10 | if [ "$(uname)" == "Darwin" ]; then
11 | machine=darwin
12 | else
13 | machine=linux
14 | fi
15 |
16 | backup_current_kubeconfig() {
17 | mv "$HOME/.kube/config" "$HOME/.kube/config.bkp" || echo "No original kubeconfig to backup was found."
18 | }
19 |
20 | run_e2e_container() {
21 | echo 'Running e2e container...'
22 | docker run --rm --interactive --detach --network host --name e2e \
23 | --volume "$(pwd):/workdir" \
24 | --workdir /workdir/tests/crd-controller \
25 | "mcr.microsoft.com/dotnet/core/sdk:3.0" \
26 | cat
27 | echo
28 | }
29 |
30 | cleanup() {
31 | echo 'Removing e2e container...'
32 | docker kill e2e > /dev/null 2>&1
33 | echo 'Removing kind e2e-test cluster'
34 | ./kind delete clusters e2e-test
35 | echo 'Restoring kubeconfig'
36 | mv "$HOME/.kube/config.bkp" "$HOME/.kube/config" || echo "No original kubeconfig to backup was found."
37 | echo 'Removing kubectl and kind binaries'
38 | rm kubectl
39 | rm kind
40 | echo 'Done!'
41 | }
42 |
43 | docker_exec() {
44 | docker exec --interactive e2e "$@"
45 | }
46 |
47 | create_kind_cluster() {
48 | K8S_VERSION=$1
49 | echo 'Installing kind...'
50 | echo 'kubernetes version' "$K8S_VERSION"
51 |
52 | curl -sfSLo kind "https://github.com/kubernetes-sigs/kind/releases/download/v$KIND_VERSION/kind-$machine-amd64"
53 | chmod +x kind
54 |
55 | curl -sfSLO https://storage.googleapis.com/kubernetes-release/release/"$K8S_VERSION"/bin/linux/amd64/kubectl
56 | chmod +x kubectl
57 |
58 | docker cp kubectl e2e:/usr/local/bin/kubectl
59 |
60 | kind_config="kind-config.yaml"
61 |
62 | TMPDIR=$HOME ./kind create cluster --name "$CLUSTER_NAME" --config tests/crd-controller/$kind_config --image "kindest/node:$K8S_VERSION"
63 |
64 | ./kind load image-archive docker-cache-api/crd-controller.tar --name "$CLUSTER_NAME"
65 | docker_exec mkdir -p /root/.kube
66 |
67 | echo 'Copying kubeconfig to container...'
68 | kubeconfig_path="$HOME/.kube/config"
69 | if [ "$machine" == "darwin" ]; then
70 | kubectl config set-cluster kind-e2e-test --insecure-skip-tls-verify=true
71 | sed -i "" 's/127.0.0.1/host.docker.internal/g' "$kubeconfig_path"
72 | fi
73 |
74 | docker cp "$kubeconfig_path" e2e:/root/.kube/config
75 | echo -n 'Waiting for cluster to be ready...'
76 | until ! grep --quiet 'NotReady' <(docker_exec kubectl get nodes --no-headers); do
77 | printf '.'
78 | sleep 1
79 | done
80 |
81 | echo '✔︎'
82 | echo
83 |
84 | docker_exec kubectl get nodes
85 | echo
86 |
87 | echo 'Cluster ready!'
88 | echo
89 | }
90 |
91 | run_test() {
92 | docker_exec dotnet test
93 | echo
94 | }
95 |
96 | main() {
97 | backup_current_kubeconfig
98 | run_e2e_container
99 | trap cleanup EXIT
100 |
101 | create_kind_cluster "$1"
102 |
103 | run_test
104 | }
105 |
106 | main "$@"
--------------------------------------------------------------------------------
/src/decrypt-api/Controllers/MonitoringController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Microsoft.AspNetCore.Authorization;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Mvc;
7 | using k8s;
8 | using k8s.Models;
9 | using Serilog;
10 | using Microsoft.Extensions.Caching.Memory;
11 |
12 | namespace Kamus.Controllers
13 | {
14 | public class MonitoringController
15 | {
16 | private readonly IKubernetes mKubernetes;
17 | private readonly ILogger mLogger = Log.ForContext();
18 | private static readonly MemoryCache _memoryCache = new MemoryCache(new MemoryCacheOptions());
19 |
20 | public MonitoringController(IKubernetes kubernetes)
21 | {
22 | mKubernetes = kubernetes;
23 | }
24 |
25 | [HttpGet]
26 | [Route("api/v1/isAlive")]
27 | public async Task IsAlive()
28 | {
29 | var isAliveCached = _memoryCache.Get("isAlive");
30 |
31 | if (isAliveCached is bool b && (bool)b)
32 | {
33 | return true;
34 | }
35 |
36 | if (!await CheckIsAlive())
37 | {
38 | return false;
39 | }
40 |
41 | _memoryCache.Set("isAlive", true, DateTimeOffset.Now.AddMinutes(5));
42 |
43 | return true;
44 | }
45 |
46 | // Accodring to Kubernetes code: Not filling in spec.namespace means "in all namespaces".
47 | // https://github.com/kubernetes/kubernetes/blob/dc3edee5f5f732df7c6b50e073c35bd20912ac6a/pkg/apis/authorization/types.go#L28
48 | private async Task CheckIsAlive()
49 | {
50 | try
51 | {
52 | var result = await mKubernetes.CreateSelfSubjectAccessReviewAsync(new V1SelfSubjectAccessReview
53 | {
54 | Kind = "SelfSubjectAccessReview",
55 | ApiVersion = "authorization.k8s.io/v1",
56 | Spec = new V1SelfSubjectAccessReviewSpec
57 | {
58 | ResourceAttributes = new V1ResourceAttributes
59 | {
60 | Group = "authentication.k8s.io",
61 | Verb = "create",
62 | Resource = "tokenreviews"
63 | }
64 | }
65 | });
66 |
67 | if (!result.Status.Allowed)
68 | {
69 | mLogger.Warning("SelfSubjectAccessReview result is denied. Error: {error}, reason: {reason}", result.Status.EvaluationError, result.Status.Reason);
70 | }
71 |
72 | return result.Status.Allowed;
73 | }
74 | catch (Exception e)
75 | {
76 | mLogger.Warning(e, "SelfSubjectAccessReview check failed");
77 | return false;
78 | }
79 | }
80 |
81 | [HttpGet]
82 | [Route("")]
83 | public string Welcome()
84 | {
85 | return "welcome";
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/tests/unit/KeyManagment/SymmetricKeyManagmentTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Security.Cryptography;
3 | using System.Threading.Tasks;
4 | using Kamus.KeyManagement;
5 | using Xunit;
6 |
7 | namespace unit.KeyManagement
8 | {
9 | public class SymmetricKeyManagementTests
10 | {
11 | private static readonly byte[] Key = Convert.FromBase64String("tWG4dk8ARsETnFL3jCf1xtMVe05imlx9vimER7iky2s =");
12 |
13 | [Fact]
14 | public async Task Get_ReturnsCorrectValues()
15 | {
16 | var kms = new SymmetricKeyManagement(Key, false);
17 | var expected = "hello";
18 | var encrypted = await kms.Encrypt(expected, "sa");
19 | var decrypted = await kms.Decrypt(encrypted, "sa");
20 |
21 | Assert.Equal(expected, decrypted);
22 | }
23 |
24 | [Fact]
25 | public async Task Get_ReturnsCorrectValuesWhenDeriviationEnabled()
26 | {
27 | var kms = new SymmetricKeyManagement(Key, true);
28 | var expected = "hello";
29 | var encrypted = await kms.Encrypt(expected, "sa");
30 | var decrypted = await kms.Decrypt(encrypted, "sa");
31 |
32 | Assert.Equal(expected, decrypted);
33 | }
34 |
35 | [Fact]
36 | public async Task Get_ReturnsCorrectValuesForDifferentServiceAccounts()
37 | {
38 | var kms = new SymmetricKeyManagement(Key, false);
39 | var expected = "hello";
40 | var encryptedSa1 = await kms.Encrypt(expected, "sa1");
41 | var encryptedSa2 = await kms.Encrypt(expected, "sa2");
42 | var decryptedSa1 = await kms.Decrypt(encryptedSa2, "sa1");
43 | var decryptedSa2 = await kms.Decrypt(encryptedSa1, "sa2");
44 |
45 | Assert.Equal(expected, decryptedSa1);
46 | Assert.Equal(expected, decryptedSa2);
47 | }
48 |
49 | [Fact]
50 | public async Task Get_ReturnsInvalidValuesForDifferentServiceAccountsWhenDeriviationEnabled()
51 | {
52 | var kms = new SymmetricKeyManagement(Key, true);
53 | var expected = "hello";
54 | var encryptedSa1 = await kms.Encrypt(expected, "sa1");
55 | var encryptedSa2 = await kms.Encrypt(expected, "sa2");
56 | await Assert.ThrowsAsync(async () => await kms.Decrypt(encryptedSa2, "sa1"));
57 | await Assert.ThrowsAsync(async () => await kms.Decrypt(encryptedSa1, "sa2"));
58 | }
59 |
60 | [Fact]
61 | public async Task RegressionTest()
62 | {
63 | var kms = new SymmetricKeyManagement(Key, false);
64 | var expected = "hello";
65 | var encrypted = "C4gChhspnTa5yVqYmSitrg==:tr0Ke6OGUaUa8KZgMJg14g==";
66 | var decrypted = await kms.Decrypt(encrypted, "sa");
67 |
68 | Assert.Equal(expected, decrypted);
69 | }
70 |
71 | [Fact]
72 | public async Task RegressionTestWithDeriviation()
73 | {
74 | var kms = new SymmetricKeyManagement(Key, true);
75 | var expected = "hello";
76 | var encrypted = "VnZkifeNdxI7NWMbjr/MZg==:qskLf4Z57DC9HBTe6+IEkA==";
77 | var decrypted = await kms.Decrypt(encrypted, "sa");
78 |
79 | Assert.Equal(expected, decrypted);
80 | }
81 |
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/tests/integration/GoogleCloudKeyManagementTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using Google.Cloud.Kms.V1;
5 | using Grpc.Core;
6 | using Kamus.KeyManagement;
7 | using Microsoft.Extensions.Configuration;
8 | using Xunit;
9 |
10 | namespace integration
11 | {
12 | public class GoogleCloudKeyManagementTests
13 | {
14 | private readonly GoogleCloudKeyManagement mGoogleCloudKeyManagement;
15 | private readonly IConfiguration mConfiguration;
16 |
17 | public GoogleCloudKeyManagementTests()
18 | {
19 | mConfiguration = new ConfigurationBuilder()
20 | .AddJsonFile("settings.json")
21 | .AddEnvironmentVariables().Build();
22 |
23 | var stream = new MemoryStream();
24 | var writer = new StreamWriter(stream);
25 | File.WriteAllText("creds.json", System.Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS"));
26 | System.Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", "creds.json");
27 | var location = mConfiguration.GetValue("KeyManagement:GoogleKms:Location");
28 | var keyRingName = mConfiguration.GetValue("KeyManagement:GoogleKms:KeyRingName");
29 | var protectionLevel = mConfiguration.GetValue("KeyManagement:GoogleKms:ProtectionLevel");
30 | var projectId = mConfiguration.GetValue("KeyManagement:GoogleKms:ProjectId");
31 |
32 | mGoogleCloudKeyManagement = new GoogleCloudKeyManagement(
33 | KeyManagementServiceClient.Create(),
34 | projectId,
35 | keyRingName,
36 | location,
37 | protectionLevel,
38 | "30");
39 | }
40 |
41 | [Fact]
42 | public async Task TestFullFlow()
43 | {
44 | var sa = "sa:namespace";
45 | var data = "The quick brown fox jumps over the lazy dog";
46 | var encrypted = await mGoogleCloudKeyManagement.Encrypt(data, sa);
47 | var decrypted = await mGoogleCloudKeyManagement.Decrypt(encrypted, sa);
48 |
49 | Assert.Equal(data, decrypted);
50 | }
51 |
52 | [Fact]
53 | public async Task RegresssionTests()
54 | {
55 | var sa = "sa:namespace";
56 | var data = "data";
57 | var encrypted = "CiQAk2+d4VDCX+mbfEUBV+2yvzWbWFuWe3qVI+0IQQ6tgH+xnmESMBIuCgyzGbLErJFqtftMuhUSDAq8ngWQqfd/eTFetBoQAZty6/68gUNAU++kCbx20Q==";
58 | var decrypted = await mGoogleCloudKeyManagement.Decrypt(encrypted, sa);
59 |
60 | Assert.Equal(data, decrypted);
61 |
62 | }
63 |
64 | [Fact]
65 | public async Task DecryptWithDifferentSAFails()
66 | {
67 | var sa = "sa:namespace";
68 | var sa2 = "sa2:namespace";
69 | var data = "data";
70 | var encrypted = await mGoogleCloudKeyManagement.Encrypt(data, sa);
71 |
72 | // To make sure the key does exist
73 | await mGoogleCloudKeyManagement.Encrypt(data, sa2);
74 | // ===============================
75 |
76 | await Assert.ThrowsAsync(async () => await mGoogleCloudKeyManagement.Decrypt(encrypted, "SA2:namespace"));
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/cli/.snyk:
--------------------------------------------------------------------------------
1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
2 | version: v1.14.1
3 | ignore: {}
4 | # patches apply the minimum changes required to fix a vulnerability
5 | patch:
6 | SNYK-JS-LODASH-450202:
7 | - adal-node > async > lodash:
8 | patched: '2019-07-04T03:58:07.989Z'
9 | - caporal > tabtab > inquirer > lodash:
10 | patched: '2019-07-04T03:58:07.989Z'
11 | - snyk > snyk-config > lodash:
12 | patched: '2019-07-04T06:26:56.428Z'
13 | - snyk > lodash:
14 | patched: '2019-07-04T06:26:56.428Z'
15 | - snyk > snyk-nodejs-lockfile-parser > lodash:
16 | patched: '2019-07-04T06:26:56.428Z'
17 | - snyk > snyk-mvn-plugin > lodash:
18 | patched: '2019-07-04T06:26:56.428Z'
19 | - snyk > @snyk/dep-graph > lodash:
20 | patched: '2019-07-04T06:26:56.428Z'
21 | - snyk > snyk-nuget-plugin > lodash:
22 | patched: '2019-07-04T06:26:56.428Z'
23 | - snyk > inquirer > lodash:
24 | patched: '2019-07-04T06:26:56.428Z'
25 | - snyk > snyk-nodejs-lockfile-parser > graphlib > lodash:
26 | patched: '2019-07-04T06:26:56.428Z'
27 | - snyk > snyk-go-plugin > graphlib > lodash:
28 | patched: '2019-07-04T06:26:56.428Z'
29 | - snyk > @snyk/dep-graph > graphlib > lodash:
30 | patched: '2019-07-04T06:26:56.428Z'
31 | - snyk > snyk-php-plugin > @snyk/composer-lockfile-parser > lodash:
32 | patched: '2019-07-04T06:26:56.428Z'
33 | SNYK-JS-LODASH-567746:
34 | - caporal > lodash:
35 | patched: '2020-05-01T06:59:12.326Z'
36 | - snyk > lodash:
37 | patched: '2020-05-01T06:59:12.326Z'
38 | - adal-node > async > lodash:
39 | patched: '2020-05-01T06:59:12.326Z'
40 | - snyk > @snyk/dep-graph > lodash:
41 | patched: '2020-05-01T06:59:12.326Z'
42 | - snyk > inquirer > lodash:
43 | patched: '2020-05-01T06:59:12.326Z'
44 | - snyk > snyk-config > lodash:
45 | patched: '2020-05-01T06:59:12.326Z'
46 | - snyk > snyk-mvn-plugin > lodash:
47 | patched: '2020-05-01T06:59:12.326Z'
48 | - snyk > snyk-nodejs-lockfile-parser > lodash:
49 | patched: '2020-05-01T06:59:12.326Z'
50 | - snyk > snyk-nuget-plugin > lodash:
51 | patched: '2020-05-01T06:59:12.326Z'
52 | - caporal > tabtab > inquirer > lodash:
53 | patched: '2020-05-01T06:59:12.326Z'
54 | - snyk > @snyk/dep-graph > graphlib > lodash:
55 | patched: '2020-05-01T06:59:12.326Z'
56 | - snyk > snyk-go-plugin > graphlib > lodash:
57 | patched: '2020-05-01T06:59:12.326Z'
58 | - snyk > snyk-nodejs-lockfile-parser > graphlib > lodash:
59 | patched: '2020-05-01T06:59:12.326Z'
60 | - snyk > @snyk/snyk-cocoapods-plugin > @snyk/dep-graph > lodash:
61 | patched: '2020-05-01T06:59:12.326Z'
62 | - snyk > snyk-nuget-plugin > dotnet-deps-parser > lodash:
63 | patched: '2020-05-01T06:59:12.326Z'
64 | - snyk > snyk-php-plugin > @snyk/composer-lockfile-parser > lodash:
65 | patched: '2020-05-01T06:59:12.326Z'
66 | - snyk > @snyk/snyk-cocoapods-plugin > @snyk/dep-graph > graphlib > lodash:
67 | patched: '2020-05-01T06:59:12.326Z'
68 | - snyk > @snyk/snyk-cocoapods-plugin > @snyk/cocoapods-lockfile-parser > @snyk/ruby-semver > lodash:
69 | patched: '2020-05-01T06:59:12.326Z'
70 | - snyk > @snyk/snyk-cocoapods-plugin > @snyk/cocoapods-lockfile-parser > @snyk/dep-graph > graphlib > lodash:
71 | patched: '2020-05-01T06:59:12.326Z'
72 |
--------------------------------------------------------------------------------
/tests/integration/EnvelopeDecoratorIntegrationTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text.RegularExpressions;
4 | using System.Threading.Tasks;
5 | using Kamus.KeyManagement;
6 | using Microsoft.Azure.KeyVault;
7 | using Microsoft.Extensions.Configuration;
8 | using Xunit;
9 |
10 | namespace integration
11 | {
12 | public class EnvelopeDecoratorIntegrationTests
13 | {
14 | private IKeyManagement mDecorator;
15 | private readonly IConfiguration mConfiguration;
16 |
17 | public EnvelopeDecoratorIntegrationTests()
18 | {
19 | mConfiguration = new ConfigurationBuilder().AddJsonFile("settings.json").AddEnvironmentVariables().Build();
20 | InitializeKeyManagement();
21 | }
22 |
23 | private void InitializeKeyManagement()
24 | {
25 | var clientId = mConfiguration.GetValue("ActiveDirectory:ClientId");
26 | var clientSecret = mConfiguration.GetValue("ActiveDirectory:ClientSecret");
27 | var keyVaultClient = new KeyVaultClient(((authority, resource, scope) =>
28 | Utils.AuthenticationCallback(clientId, clientSecret, authority, resource, scope)));
29 | var keyVaultManagement = new AzureKeyVaultKeyManagement(keyVaultClient, mConfiguration);
30 | mDecorator = new EnvelopeEncryptionDecorator(keyVaultManagement, 15);
31 | }
32 |
33 | [Fact]
34 | public async Task DataIsLessThenMaximumConfiguration_NoEnvelope()
35 | {
36 | var encryptedData = await mDecorator.Encrypt("123", "a-service-account");
37 | Assert.DoesNotContain(encryptedData, "env$");
38 | }
39 |
40 | [Fact]
41 | public async Task DataIsLessThenMaximumConfiguration_DecryptsBackCorrectly()
42 | {
43 | var data = "123";
44 | var encryptedData = await mDecorator.Encrypt(data, "a-service-account");
45 |
46 | var decryptedString = await mDecorator.Decrypt(encryptedData, "a-service-account");
47 |
48 | Assert.Equal(data, decryptedString);
49 |
50 | }
51 |
52 | [Fact]
53 | public async Task DataIsMoreThenMaximumConfiguration_EnvelopeApplied_DecryptsBackCorrectly()
54 | {
55 | var randomString = new string('*', 16);
56 | var encryptedData = await mDecorator.Encrypt(randomString, "a-service-account");
57 | Assert.Contains("env$", encryptedData);
58 |
59 | var decryptedString = await mDecorator.Decrypt(encryptedData, "a-service-account");
60 |
61 | Assert.Equal(randomString, decryptedString);
62 | }
63 |
64 | [Fact]
65 | public async Task RegressionTest()
66 | {
67 | var randomString = new string('*', 16);
68 | var encryptedData = "env$Ux2a2llsVdGY5/FsQ+G79A0WJfvjzddXS2jQk+hkY7zzJh6k29Kezkg12M6OorA0cYA7nXByBAOilNRbIWsE2+36ygCeqZg8PUMxBDPGOVE4ANYI+3abrl4aXmUSIy8uDrbISdl4RCVhwFNO2BCgecwioNv5mFGNzonELR1FcX1cLShezibWJehcptPExFM9Sey+GcXixPS2Fi6IivgTjzwFqHMze20wLlDFPkiFHN9CcUsHOt9ntxUFujtaMpZTJecTvXnhwknQOqQ3KNBBWyOgX65Svm45f4YEP5WZmdvF2mBSHTdkQ7AkfKZZV15p4dN1mzxs3raHTR2Qp366Sg==$nzx0kjzDxQ/d4rYfDUfBhw==:BSRpNCPL7R3uKFanSnjUw2E55xXcqIHvMRKWC1e+zVU=";
69 |
70 | var decryptedString = await mDecorator.Decrypt(encryptedData, "a-service-account");
71 |
72 | Assert.Equal(randomString, decryptedString);
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/src/key-managment/AzureKeyVaultKeyManagement.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Security.Cryptography;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.WebUtilities;
7 | using Microsoft.Azure.KeyVault;
8 | using Microsoft.Azure.KeyVault.Models;
9 | using Microsoft.Extensions.Configuration;
10 | using Serilog;
11 |
12 | namespace Kamus.KeyManagement
13 | {
14 | public class AzureKeyVaultKeyManagement : IKeyManagement
15 | {
16 | private readonly IKeyVaultClient mKeyVaultClient;
17 | private readonly string mKeyVaultName;
18 | private readonly string mKeyType;
19 | private readonly short mKeyLength;
20 | private readonly ILogger mLogger = Log.ForContext();
21 |
22 | public AzureKeyVaultKeyManagement(IKeyVaultClient keyVaultClient,
23 | IConfiguration configuration)
24 | {
25 | mKeyVaultClient = keyVaultClient;
26 |
27 | mKeyVaultName = configuration["KeyManagement:KeyVault:Name"];
28 | mKeyType = configuration["KeyManagement:KeyVault:KeyType"];
29 |
30 | if (!short.TryParse(configuration["KeyManagement:KeyVault:KeyLength"], out mKeyLength)){
31 | throw new Exception($"Expected key length int, got {configuration["KeyManagement:KeyVault:KeyLength"]}");
32 | }
33 | }
34 |
35 | public async Task Decrypt(string encryptedData, string serviceAccountId)
36 | {
37 | var hash = KeyIdCreator.Create(serviceAccountId);
38 |
39 | var keyId = $"https://{mKeyVaultName}.vault.azure.net/keys/{hash}";
40 | try
41 | {
42 | var encryptionResult =
43 | await mKeyVaultClient.DecryptAsync(keyId, "RSA-OAEP", Convert.FromBase64String(encryptedData));
44 |
45 | return Encoding.UTF8.GetString(encryptionResult.Result);
46 | }
47 | catch (KeyVaultErrorException e)
48 | {
49 | throw new DecryptionFailureException("KeyVault decryption failed", e);
50 | }
51 | catch (FormatException e)
52 | {
53 | throw new DecryptionFailureException("Invalid encrypted data format - probably an issue with the encrytion", e);
54 | }
55 | }
56 |
57 | public async Task Encrypt(string data, string serviceAccountId, bool createKeyIfMissing = true)
58 | {
59 | var hash = KeyIdCreator.Create(serviceAccountId);
60 |
61 | var keyId = $"https://{mKeyVaultName}.vault.azure.net/keys/{hash}";
62 |
63 | try
64 | {
65 | await mKeyVaultClient.GetKeyAsync(keyId);
66 | }
67 | catch (KeyVaultErrorException e) when (e.Response.StatusCode == HttpStatusCode.NotFound && createKeyIfMissing)
68 | {
69 | mLogger.Information(
70 | "KeyVault key was not found for service account id {serviceAccount}, creating new one.",
71 | serviceAccountId);
72 |
73 | await mKeyVaultClient.CreateKeyAsync($"https://{mKeyVaultName}.vault.azure.net", hash, mKeyType, mKeyLength);
74 | }
75 |
76 | var encryptionResult = await mKeyVaultClient.EncryptAsync(keyId, "RSA-OAEP", Encoding.UTF8.GetBytes(data));
77 |
78 | return Convert.ToBase64String(encryptionResult.Result);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/cli/README.md:
--------------------------------------------------------------------------------
1 | [](https://badge.fury.io/js/%40soluto-asurion%2Fkamus-cli)
2 | [](https://snyk.io/test/github/soluto/kamus) [](https://hub.docker.com/r/soluto/kamus-cli "Get your own image badge on microbadger.com")
3 |
4 | ## Kamus CLI
5 |
6 | This cli was created to provide an easy interface to interact with Kamus API.
7 |
8 | It supports azure device flow authentication out of the box.
9 |
10 | To install, use the following NPM command:
11 | ```
12 | npm install -g @soluto-asurion/kamus-cli
13 | ```
14 | Alternatively, you can use docker to run the CLI (for example, to run it inside the cluster when the encryptor is deployed without ingress):
15 | ```
16 | docker run -it --rm ghcr.io/soluto/kamus-cli encrypt
17 | ```
18 | Or, using kubectl:
19 | ```
20 | kubectl run -it --rm --restart=Never kamus-cli --image=soluto/kamus-cli -- encrypt
21 | ```
22 | ---
23 |
24 | #### Supported commands:
25 |
26 | ##### Encrypt
27 | `kamus-cli encrypt --secret --service-account --namespace --kamus-url `
28 |
29 | ---
30 | #### How to enable azure active directory authentication
31 | You need working active directory [tenant](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-create-new-tenant) and designated [native app registration](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-register-an-app), Then just set all the `auth` prefixed options.
32 | Once the user will run the cli with the auth options, he will get a small code and and azure URL to login into.
33 |
34 | ---
35 | ##### CLI options:
36 |
37 | | Option | Required | Description | Default Value |
38 | | ------------------- | ------------ | ----------------------------------------------- | ------------- |
39 | | --auth-tenant | false | azure authentication tenant id | |
40 | | --auth-application | false | azure authentication application id | |
41 | | --auth-resource | false | azure authentication resource id | |
42 | | --cert-fingerprint | false | [certificate fingerprint](http://hassansin.github.io/certificate-pinning-in-nodejs) of encrypt api for validation | |
43 | | --kamus-url | true | url of kamus encrypt api | |
44 | | --allow-insecure-url | false | allow or block non https endpoints | false |
45 | | --log-level | false | specify global logger level |
46 | | --log-flag <\[no-\](date|inline|colorful)> | false | the prefix no- represent negation. date: whether to print date. default value is false. inline: each log record output in one line. default value is false. colorful: whether to print with colors. default value is true.
47 | | --log-output | false | specify the output path (default behavior is output directory to stdout).
48 | | --log-encoding | false | specify the log file's encoding.
49 | | --secret or --secret-file | true | the secret to encrypt, or the file containing it | |
--------------------------------------------------------------------------------
/src/crd-controller/HealthChecks/KubernetesPermissionsHelthCheck.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using k8s;
6 | using k8s.Models;
7 | using Microsoft.Extensions.Caching.Memory;
8 | using Microsoft.Extensions.Diagnostics.HealthChecks;
9 | using Serilog;
10 |
11 | namespace CustomResourceDescriptorController.HealthChecks
12 | {
13 | public class KubernetesPermissionsHelthCheck : IHealthCheck
14 | {
15 | private readonly IKubernetes mKubernetes;
16 | private readonly ILogger mLogger = Log.ForContext();
17 | private static readonly MemoryCache _memoryCache = new MemoryCache(new MemoryCacheOptions());
18 |
19 | public KubernetesPermissionsHelthCheck(IKubernetes kubernetes)
20 | {
21 | mKubernetes = kubernetes;
22 | }
23 |
24 | public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
25 | {
26 | var isAliveCached = _memoryCache.Get("isAlive");
27 |
28 | if (isAliveCached is bool b && (bool)b)
29 | {
30 | return HealthCheckResult.Healthy("Kubernetes permissions configured correctly");
31 | }
32 |
33 | if (!await CheckIsAlive())
34 | {
35 | return HealthCheckResult.Unhealthy("Kubernetes permissions misconfigured");
36 | }
37 |
38 | _memoryCache.Set("isAlive", true, DateTimeOffset.Now.AddMinutes(20));
39 |
40 | return HealthCheckResult.Healthy("Kubernetes permissions configured correctly");
41 | }
42 |
43 | private async Task CheckIsAlive()
44 | {
45 | var results = await Task.WhenAll(new[] {
46 | CheckPermissions("soluto.com", "kamussecrets", "watch"),
47 | CheckPermissions("", "secrets", "create"),
48 | CheckPermissions("", "secrets", "delete")
49 | });
50 |
51 | return results.All(r => r);
52 | }
53 |
54 | private async Task CheckPermissions(string group, string resource, string verb)
55 | {
56 | try
57 | {
58 | var result = await mKubernetes.CreateSelfSubjectAccessReviewAsync(new V1SelfSubjectAccessReview
59 | {
60 | Kind = "SelfSubjectAccessReview",
61 | ApiVersion = "authorization.k8s.io/v1",
62 | Spec = new V1SelfSubjectAccessReviewSpec
63 | {
64 | ResourceAttributes = new V1ResourceAttributes
65 | {
66 | Group = group,
67 | Verb = verb,
68 | Resource = resource
69 | }
70 | }
71 | });
72 |
73 | if (!result.Status.Allowed)
74 | {
75 | mLogger.Warning("SelfSubjectAccessReview result is denied. Error: {error}, reason: {reason}, group: {group}, verb: {verb}, resource: {resource}", result.Status.EvaluationError, result.Status.Reason, group, verb, resource);
76 | }
77 |
78 | return result.Status.Allowed;
79 | }
80 | catch (Exception e)
81 | {
82 | mLogger.Warning(e, "SelfSubjectAccessReview check failed");
83 | return false;
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/decrypt-api/Controllers/DecryptController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Threading.Tasks;
4 | using System.Linq;
5 | using Kamus.Models;
6 | using k8s;
7 | using Microsoft.AspNetCore.Authorization;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.Rest;
10 | using Kamus.Extensions;
11 | using Kamus.KeyManagement;
12 | using Serilog;
13 |
14 | namespace Kamus.Controllers
15 | {
16 |
17 | public class DecryptController : Controller
18 | {
19 | private readonly IKubernetes mKubernetes;
20 | private readonly IKeyManagement mKeyManagement;
21 | private readonly ILogger mAuditLogger = Log.ForContext().AsAudit();
22 | private readonly ILogger mLogger = Log.ForContext();
23 |
24 | //see: https://github.com/kubernetes/kubernetes/blob/d5803e596fc8aba17aa8c74a96aff9c73bb0f1da/staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount/util.go#L27
25 | private const string ServiceAccountUsernamePrefix = "system:serviceaccount:";
26 |
27 | public DecryptController(IKubernetes kubernetes, IKeyManagement keyManagement)
28 | {
29 | mKubernetes = kubernetes;
30 | mKeyManagement = keyManagement;
31 | }
32 |
33 | [HttpPost]
34 | [Route("api/v1/decrypt")]
35 | [Authorize(AuthenticationSchemes = "kubernetes")]
36 | public async Task Decrypt([FromBody] DecryptRequest body)
37 | {
38 | if (!ModelState.IsValid)
39 | {
40 | mAuditLogger.Warning("Bad request to Decrypt API: {sourceIP} {validationState}",
41 | Request.HttpContext.Connection.RemoteIpAddress,
42 | ModelState.ValidationState);
43 | return BadRequest("One or more of required fields doesn't present in the request body.");
44 | }
45 |
46 | var serviceAccountUserName = User.Claims.FirstOrDefault(claim => claim.Type == "name")?.Value;
47 |
48 | if (string.IsNullOrEmpty(serviceAccountUserName) ||
49 | !serviceAccountUserName.StartsWith(ServiceAccountUsernamePrefix, StringComparison.InvariantCulture))
50 | {
51 | mAuditLogger.Information("Unauthorized decrypt request, SourceIP: {sourceIp}, ServiceAccount User Name: {id}",
52 | Request.HttpContext.Connection.RemoteIpAddress,
53 | serviceAccountUserName);
54 |
55 | return StatusCode(403);
56 | }
57 |
58 | var id = serviceAccountUserName.Replace(ServiceAccountUsernamePrefix, "");
59 |
60 | mAuditLogger.Information("Decryption request started, SourceIP: {sourceIp}, ServiceAccount User Name: {id}",
61 | Request.HttpContext.Connection.RemoteIpAddress,
62 | id);
63 |
64 | try
65 | {
66 | var data = await mKeyManagement.Decrypt(body.EncryptedData, id);
67 |
68 | mAuditLogger.Information("Decryption request succeeded, SourceIP: {sourceIp}, ServiceAccount user Name: {sa}",
69 | Request.HttpContext.Connection.RemoteIpAddress,
70 | id);
71 | return Content(data);
72 | }
73 | catch (DecryptionFailureException e)
74 | {
75 | mLogger.Warning(e, "Decryption request failed, SourceIP: {sourceIp}, ServiceAccount: {sa}",
76 | Request.HttpContext.Connection.RemoteIpAddress,
77 | serviceAccountUserName);
78 | return StatusCode(400);
79 | }
80 | }
81 |
82 |
83 | }
84 | }
85 |
--------------------------------------------------------------------------------