Skip to content

chore(controlplane): Allow to setup NATS token based authentication #1944

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

Merged
merged 1 commit into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion app/controlplane/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,17 @@ func newNatsConnection(c *conf.Bootstrap_NatsServer) (*nats.Conn, error) {
return nil, nil
}

nc, err := nats.Connect(uri)
var opts []nats.Option
if c.GetAuthentication() != nil {
switch c.GetAuthentication().(type) {
case *conf.Bootstrap_NatsServer_Token:
opts = append(opts, nats.Token(c.GetToken()))
default:
return nil, fmt.Errorf("unsupported nats authentication type: %T", c.GetAuthentication())
}
}

nc, err := nats.Connect(uri, opts...)
if err != nil {
return nil, fmt.Errorf("failed to connect to nats: %w", err)
}
Expand Down
383 changes: 209 additions & 174 deletions app/controlplane/internal/conf/controlplane/config/v1/conf.pb.go

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ message Bootstrap {
message NatsServer {
// Connection URI
string uri = 1 [(buf.validate.field).string.min_len = 1];
// TODO: add authentication options
oneof authentication {
// Token based authentication
string token = 2 [(buf.validate.field).string.min_len = 1];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of curiosity, would this force us to use token or can the current deployments keep working?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the tests that I could run, since the authentication field is optional, nothing will happen. The validation on the token happens if the token field is in place. Some examples:

With this configuration no token is set:

server:
  http:
    addr: 0.0.0.0:8000
    timeout: 10s
  http_metrics:
    addr: 0.0.0.0:0
  grpc:
    addr: 0.0.0.0:9000

# Way more configuration here

nats_server:
 uri: nats://0.0.0.0:4222

We can run the server:

go run ./cmd/... --conf ./configs
DEBUG msg=config loaded: config.devel.yaml format: yaml
2025-04-03T10:48:17.690+0200    INFO    {"component": "credentials/vault", "msg": "configuring vault", "address": "http://0.0.0.0:8200", "mount_path": "secret", "prefix": "chainloop-devel", "role": "writer"}
2025-04-03T10:48:17.698+0200    INFO    {"component": "plugins", "msg": "loading plugins", "dir": "./plugins/bin", "pattern": "chainloop-plugin-*"}
2025-04-03T10:48:17.698+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=dependency-track, version=1.6, expectedMaterials=[SBOM_CYCLONEDX_JSON]"}
2025-04-03T10:48:17.698+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=smtp, version=1.0, expectedMaterials=[]"}
2025-04-03T10:48:17.698+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=discord-webhook, version=1.1, expectedMaterials=[]"}
2025-04-03T10:48:17.698+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=guac, version=1.0, expectedMaterials=[SBOM_CYCLONEDX_JSON SBOM_SPDX_JSON]"}
2025-04-03T10:48:17.698+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=slack-webhook, version=1.0, expectedMaterials=[]"}
2025-04-03T10:48:17.698+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=webhook, version=1.0, expectedMaterials=[SBOM_CYCLONEDX_JSON SBOM_SPDX_JSON]"}
2025-04-03T10:48:17.731+0200    INFO    {"component": "natsAuditLogPublisher", "msg": "Stream Created or Updated", "name": "chainloop-audit", "subject": "audit.>"}

For all the following cases the NATS does not have any authentication configured.

Now let's add a token to the connection:

server:
  http:
    addr: 0.0.0.0:8000
    timeout: 10s
  http_metrics:
    addr: 0.0.0.0:0
  grpc:
    addr: 0.0.0.0:9000

# Way more configuration here

nats_server:
 uri: nats://0.0.0.0:4222
 token: "notasecret"
go run ./cmd/... --conf ./configs
DEBUG msg=config loaded: config.devel.yaml format: yaml
2025-04-03T10:49:26.621+0200    INFO    {"component": "credentials/vault", "msg": "configuring vault", "address": "http://0.0.0.0:8200", "mount_path": "secret", "prefix": "chainloop-devel", "role": "writer"}
2025-04-03T10:49:26.632+0200    INFO    {"component": "plugins", "msg": "loading plugins", "dir": "./plugins/bin", "pattern": "chainloop-plugin-*"}
2025-04-03T10:49:26.632+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=dependency-track, version=1.6, expectedMaterials=[SBOM_CYCLONEDX_JSON]"}
2025-04-03T10:49:26.632+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=smtp, version=1.0, expectedMaterials=[]"}
2025-04-03T10:49:26.632+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=discord-webhook, version=1.1, expectedMaterials=[]"}
2025-04-03T10:49:26.632+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=guac, version=1.0, expectedMaterials=[SBOM_CYCLONEDX_JSON SBOM_SPDX_JSON]"}
2025-04-03T10:49:26.632+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=slack-webhook, version=1.0, expectedMaterials=[]"}
2025-04-03T10:49:26.632+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=webhook, version=1.0, expectedMaterials=[SBOM_CYCLONEDX_JSON SBOM_SPDX_JSON]"}
2025-04-03T10:49:26.648+0200    INFO    {"component": "natsAuditLogPublisher", "msg": "Stream Created or Updated", "name": "chainloop-audit", "subject": "audit.>"}

The server starts correctly. If we now, leave the token property set and empty:

server:
  http:
    addr: 0.0.0.0:8000
    timeout: 10s
  http_metrics:
    addr: 0.0.0.0:0
  grpc:
    addr: 0.0.0.0:9000

# Way more configuration here

nats_server:
 uri: nats://0.0.0.0:4222
 token: ""
go run ./cmd/... --conf ./configs
DEBUG msg=config loaded: config.devel.yaml format: yaml
panic: validation error:
         - nats_server.token: value length must be at least 1 characters [string.min_len]

Let's activate the NATS authentication and try without token:

server:
  http:
    addr: 0.0.0.0:8000
    timeout: 10s
  http_metrics:
    addr: 0.0.0.0:0
  grpc:
    addr: 0.0.0.0:9000

# Way more configuration here

nats_server:
 uri: nats://0.0.0.0:4222
go run ./cmd/... --conf ./configs
DEBUG msg=config loaded: config.devel.yaml format: yaml
2025-04-03T10:56:11.060+0200    INFO    {"component": "credentials/vault", "msg": "configuring vault", "address": "http://0.0.0.0:8200", "mount_path": "secret", "prefix": "chainloop-devel", "role": "writer"}
2025-04-03T10:56:11.068+0200    INFO    {"component": "plugins", "msg": "loading plugins", "dir": "./plugins/bin", "pattern": "chainloop-plugin-*"}
2025-04-03T10:56:11.068+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=dependency-track, version=1.6, expectedMaterials=[SBOM_CYCLONEDX_JSON]"}
2025-04-03T10:56:11.068+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=smtp, version=1.0, expectedMaterials=[]"}
2025-04-03T10:56:11.068+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=discord-webhook, version=1.1, expectedMaterials=[]"}
2025-04-03T10:56:11.068+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=guac, version=1.0, expectedMaterials=[SBOM_CYCLONEDX_JSON SBOM_SPDX_JSON]"}
2025-04-03T10:56:11.068+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=slack-webhook, version=1.0, expectedMaterials=[]"}
2025-04-03T10:56:11.068+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=webhook, version=1.0, expectedMaterials=[SBOM_CYCLONEDX_JSON SBOM_SPDX_JSON]"}
2025-04-03T10:56:11.087+0200    INFO    {"msg": "closing the data resources"}
panic: failed to connect to nats: nats: Authorization Violation

With token:

server:
  http:
    addr: 0.0.0.0:8000
    timeout: 10s
  http_metrics:
    addr: 0.0.0.0:0
  grpc:
    addr: 0.0.0.0:9000

# Way more configuration here

nats_server:
 uri: nats://0.0.0.0:4222
 token: "notasecret"
go run ./cmd/... --conf ./configs
DEBUG msg=config loaded: config.devel.yaml format: yaml
2025-04-03T10:57:51.161+0200    INFO    {"component": "credentials/vault", "msg": "configuring vault", "address": "http://0.0.0.0:8200", "mount_path": "secret", "prefix": "chainloop-devel", "role": "writer"}
2025-04-03T10:57:51.170+0200    INFO    {"component": "plugins", "msg": "loading plugins", "dir": "./plugins/bin", "pattern": "chainloop-plugin-*"}
2025-04-03T10:57:51.171+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=dependency-track, version=1.6, expectedMaterials=[SBOM_CYCLONEDX_JSON]"}
2025-04-03T10:57:51.171+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=smtp, version=1.0, expectedMaterials=[]"}
2025-04-03T10:57:51.171+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=discord-webhook, version=1.1, expectedMaterials=[]"}
2025-04-03T10:57:51.171+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=guac, version=1.0, expectedMaterials=[SBOM_CYCLONEDX_JSON SBOM_SPDX_JSON]"}
2025-04-03T10:57:51.171+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=slack-webhook, version=1.0, expectedMaterials=[]"}
2025-04-03T10:57:51.171+0200    INFO    {"component": "plugins", "msg": "loaded", "type": "built-in", "plugin": "id=webhook, version=1.0, expectedMaterials=[SBOM_CYCLONEDX_JSON SBOM_SPDX_JSON]"}
2025-04-03T10:57:51.195+0200    INFO    {"component": "natsAuditLogPublisher", "msg": "Stream Created or Updated", "name": "chainloop-audit", "subject": "audit.>"}

Basically the key here is not rendering the token field on the nats_server configuration object if there is no authentication.

This is the default configuration being rendered if the token value is not passed:

stringData:
  config.secret.yaml: |
    data:
      database:
        driver: pgx
        source: postgresql://chainloop:chainlooppwd@chainloop-postgresql:5432/chainloop-cp
    nats_server: 
      uri: "nats://aaa:4222"

    credentials_service:      
      secretPrefix: "chainloop"
      vault:
        address: "http://chainloop-vault-server:8200"
        token: "notasecret"

If we pass a token with --set controlplane.nats.token="notasecret"

stringData:
  config.secret.yaml: |
    data:
      database:
        driver: pgx
        source: postgresql://chainloop:chainlooppwd@chainloop-postgresql:5432/chainloop-cp
    nats_server: 
      uri: "nats://aaa:4222"
      token: "notasecret"

    credentials_service:      
      secretPrefix: "chainloop"
      vault:
        address: "http://chainloop-vault-server:8200"
        token: "notasecret"

If we pass an empty token, --set controlplane.nats.token="", it will simply not render:

stringData:
  config.secret.yaml: |
    data:
      database:
        driver: pgx
        source: postgresql://chainloop:chainlooppwd@chainloop-postgresql:5432/chainloop-cp
    nats_server: 
      uri: "nats://aaa:4222"

    credentials_service:      
      secretPrefix: "chainloop"
      vault:
        address: "http://chainloop-vault-server:8200"
        token: "notasecret"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so about the rollout, if we add the token to the clients but not to the server, would it work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's the second example, the NATS server will ignore the authentication token if it does not have any configured.

}
}
}

Expand Down
2 changes: 1 addition & 1 deletion deployment/chainloop/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Chainloop is an open source software supply chain control plane, a

type: application
# Bump the patch (not minor, not major) version on each change in the Chart Source code
version: 1.208.0
version: 1.208.1
# Do not update appVersion, this is handled automatically by the release process
appVersion: v1.0.0-rc.1

Expand Down
1 change: 1 addition & 0 deletions deployment/chainloop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@ chainloop config save \
| `controlplane.nats.enabled` | Enable events publishing through a Nats stream | `false` |
| `controlplane.nats.host` | NATS Host | `""` |
| `controlplane.nats.port` | NATS Port | `4222` |
| `controlplane.nats.token` | NATS Client authentication token | `""` |
| `controlplane.onboarding.name` | Name of the organization to onboard | |
| `controlplane.onboarding.role` | Role of the organization to onboard | |
| `controlplane.prometheus_org_metrics` | List of organizations to expose metrics for using Prometheus | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ stringData:
{{- if and .Values.controlplane.nats.enabled }}
nats_server:
uri: {{ include "controlplane.nats.connection_string" . | quote }}
{{- if ne .Values.controlplane.nats.token "" }}
token: {{ .Values.controlplane.nats.token | quote }}
{{- end }}
{{- end }}


credentials_service: {{- include "chainloop.credentials_service_settings" . | indent 6 }}

Expand Down
2 changes: 2 additions & 0 deletions deployment/chainloop/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,12 @@ controlplane:
## @param controlplane.nats.enabled Enable events publishing through a Nats stream
## @param controlplane.nats.host NATS Host
## @param controlplane.nats.port NATS Port
## @param controlplane.nats.token NATS Client authentication token
nats:
enabled: false
host: ""
port: 4222
token: ""

## @extra controlplane.onboarding.name Name of the organization to onboard
## @extra controlplane.onboarding.role Role of the organization to onboard
Expand Down
Loading