Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve detection of Secrets when generating manifests. #246

Open
dimarobert opened this issue Jul 25, 2024 · 0 comments
Open

Improve detection of Secrets when generating manifests. #246

dimarobert opened this issue Jul 25, 2024 · 0 comments

Comments

@dimarobert
Copy link

dimarobert commented Jul 25, 2024

🚀 Feature Description

As a developer, I'd like to have all values that contain secrets protected, in order to improve security when generating manifests.

The implementation right now uses the ISecretProtectionStrategy and the BaseResourceProcessor.GetSecretEnvironmentalVariables() to decide whether the environment variables contain a secret value that needs to be protected. That decision is done by checking if the environment variable names start with any of the values predefined in the ProtectorType SmartEnum. This means that:

  1. Aspirate needs to have a Protector implementation for every kind of resource that the developers want to use (think here rabbitmq, qdrant, {insert custom service that needs a secret env variable}). As long as there isn't one for a certain resource, then that resource cannot be used in the Aspire orchestration and have properly generated secrets in manifests.
  2. There is no way for the developer to use other resources than the ones supported (those that use ConnectionString_* env variable names, MSSQL, or Postgres) or create their own custom resources (that need env variables with arbitrary names that have a secret value).

For example, when trying to add a RabbitMQ Service to the orchestration using builder.AddRabbitMQ("RabbitMQService") the resulting Helm Chart for the service will have a rabbitmqservice-configmap.yaml like the one below and no rabbitmqservice-secret.yaml will be generated. Note that the password for the admin user of the RabbitMQ instance is in plain text.

apiVersion: v1
data:
  RABBITMQ_DEFAULT_USER: guest
  RABBITMQ_DEFAULT_PASS: my_secret_password
kind: ConfigMap
metadata:
  annotations: {}
  labels:
    app: rabbitmqservice
  name: rabbitmqservice

🧰 Possible Solution

Let's start with an example manifest.json

{
  "resources": {
    "example-project": {
      "type": "project.v0",
      "path": "../exampleproject/exampleproject.csproj",
      "env": {
        "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
        "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
        "OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
        "ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
        "ConnectionStrings__RabbitMQService": "{RabbitMQService.connectionString}"
      },
      "bindings": {
        "http": {
          "scheme": "http",
          "protocol": "tcp",
          "transport": "http"
        },
        "https": {
          "scheme": "https",
          "protocol": "tcp",
          "transport": "http"
        }
      }
    },
    "RabbitMQService": {
      "type": "container.v0",
      "connectionString": "amqp://guest:{RabbitMQService-password.value}@{RabbitMQService.bindings.tcp.host}:{RabbitMQService.bindings.tcp.port}",
      "image": "docker.io/library/rabbitmq:3.13-management",
      "env": {
        "RABBITMQ_DEFAULT_USER": "guest",
        "RABBITMQ_DEFAULT_PASS": "{RabbitMQService-password.value}"
      },
      "bindings": {
        "tcp": {
          "scheme": "tcp",
          "protocol": "tcp",
          "transport": "tcp",
          "targetPort": 5672
        },
        "management": {
          "scheme": "http",
          "protocol": "tcp",
          "transport": "http",
          "targetPort": 15672
        }
      }
    },
    "RabbitMQService-password": {
      "type": "parameter.v0",
      "value": "{RabbitMQService-password.inputs.value}",
      "inputs": {
        "value": {
          "type": "string",
          "secret": true,
          "default": {
            "generate": {
              "minLength": 22,
              "special": false
            }
          }
        }
      }
    }
  }
}

A generic solution for this problem would be to rely on the ParameterResource.Inputs[*].Secret boolean property. As in the example, we could assume that most of the time any value that needs to have its value kept secret is being computed from a ParameterResource that is marked as Secret. A few examples:

  1. RabbitMQService.env.RABBITMQ_DEFAULT_PASS will have to substitute the value of the RabbitMQService-password Secure Parameter to compute its value, which makes it Secure as well.
  2. example-project.env.ConnectionStrings__RabbitMQService will have its "{RabbitMQService.connectionString}" value substituted to "amqp://guest:{RabbitMQService-password.value}@{RabbitMQService.bindings.tcp.host}:{RabbitMQService.bindings.tcp.port}" which in turn will have to use the value of RabbitMQService-password.value to finish the substitution, which will make both values in the chain be considered Secure as well. (so the lookup will have to be done recursively)

With this in mind we can derive the secretness of the values when we do the substitutions in SubstituteValuesAspireManifestAction and create some sort of SecretsMap that tells us which of the substituted values are secret. Then, every time we need to decide whether a value needs to be part of the manifest/configmap or the secrets we check against this SecretsMap instead of relying on the fixed ProtectorType SmartEnum.

I created a very rough POC here to exemplify this potential solution. I've also created a small UnitTest to be able to quickly run this scenario and prove that the resulting manifest has all its secrets put in the proper place. I am not very familiar with the codebase and did not know exactly where the implementation should be, so I tried to make minimal changes and avoid changing APIs/models as much as possible (especially the *Resource classes).

🚧 Blocked by

  • nothing I am aware of
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant