├── 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 | [![npm version](https://badge.fury.io/js/%40soluto-asurion%2Fkamus-cli.svg)](https://badge.fury.io/js/%40soluto-asurion%2Fkamus-cli) 2 | [![Known Vulnerabilities](https://snyk.io/test/github/soluto/kamus/badge.svg?targetFile=cli/package.json)](https://snyk.io/test/github/soluto/kamus) [![docker hub](https://images.microbadger.com/badges/image/soluto/kamus-cli.svg)](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 | --------------------------------------------------------------------------------