diff --git a/.go-version b/.go-version index 89144dbc38f..013173af5e9 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.22.3 +1.22.6 diff --git a/Dockerfile b/Dockerfile index 38abadf4fcd..22fca632c0e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # Build stage ARG goversion # Use alpine3.18 until go-sqlite works in 3.19 -FROM --platform=${BUILDPLATFORM} golang:${goversion}-alpine3.18 as base +FROM --platform=${BUILDPLATFORM} golang:${goversion}-alpine3.20 as base WORKDIR /spire RUN apk --no-cache --update add file bash clang lld pkgconfig git make COPY go.* ./ diff --git a/doc/plugin_agent_nodeattestor_http_challenge.md b/doc/plugin_agent_nodeattestor_http_challenge.md new file mode 100644 index 00000000000..5fd14c1e3d8 --- /dev/null +++ b/doc/plugin_agent_nodeattestor_http_challenge.md @@ -0,0 +1,49 @@ +# Agent plugin: NodeAttestor "http_challenge" + +*Must be used in conjunction with the server-side http_challenge plugin* + +The `http_challenge` plugin handshakes via http to ensure the agent is running on a valid +dns name. + +The SPIFFE ID produced by the server-side `http_challenge` plugin is based on the dns name of the agent. +The SPIFFE ID has the form: + +```xml +spiffe:///spire/agent/http_challenge/ +``` + +| Configuration | Description | Default | +|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|-----------| +| `hostname` | Hostname to use for handshaking. If unset, it will be automatically detected. | | +| `agentname` | Name of this agent on the host. Useful if you have multiple agents bound to different spire servers on the same host and sharing the same port. | "default" | +| `port` | The port to listen on. If unspecified, a random value will be used. | random | +| `advertised_port` | The port to tell the server to call back on. | $port | + +If `advertised_port` != `port`, you will need to setup an http proxy between the two ports. This is useful if you already run a webserver on port 80. + +A sample configuration: + +```hcl + NodeAttestor "http_challenge" { + plugin_data { + port = 80 + } + } +``` + +## Proxies + +Say you want to validate using port 80 to be internet firewall friendly. If you already have a webserver on port 80 or want to use multiple agents with different SPIRE servers and use the same port, +you can have your webserver proxy over to the SPIRE agent(s) by setting up a proxy on `/.well-known/spiffe/nodeattestor/http_challenge/$agentname` to +`http://localhost:$port/.well-known/spiffe/nodeattestor/http_challenge/$agentname`. + +Example spire agent configuration: + +```hcl + NodeAttestor "http_challenge" { + plugin_data { + port = 8080 + advertised_port = 80 + } + } +``` diff --git a/doc/plugin_server_nodeattestor_http_challenge.md b/doc/plugin_server_nodeattestor_http_challenge.md new file mode 100644 index 00000000000..515adf500ef --- /dev/null +++ b/doc/plugin_server_nodeattestor_http_challenge.md @@ -0,0 +1,55 @@ +# Server plugin: NodeAttestor "http_challenge" + +*Must be used in conjunction with the agent-side http_challenge plugin* + +The `http_challenge` plugin handshakes via http to ensure the agent is running on a valid +dns name. + +The SPIFFE ID produced by the plugin is based on the dns name attested. +The SPIFFE ID has the form: + +```xml +spiffe:///spire/agent/http_challenge/ +``` + +| Configuration | Description | Default | +|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------| +| `allowed_dns_patterns` | A list of regular expressions to match to the hostname being attested. If none match, attestation will fail. If unset, all hostnames are allowed. | | +| `required_port` | Set to a port number to require clients to listen only on that port. If unset, all port numbers are allowed | | +| `allow_non_root_ports` | Set to true to allow ports >= 1024 to be used by the agents with the advertised_port | true | +| `tofu` | Trust on first use of the successful challenge. Can only be disabled if allow_non_root_ports=false or required_port < 1024 | true | + +A sample configuration: + +```hcl + NodeAttestor "http_challenge" { + plugin_data { + # Only match hosts that start with p, have a number, then end in example.com. Ex: 'p1.example.com' + allowed_dns_patterns = ["p[0-9]\.example\.com"] + + # Only allow clients to use port 80 + required_port = 80 + + # Change the agent's SPIFFE ID format + # agent_path_template = "/spire/agent/http_challenge/{{ .Hostname }}" + } + } +``` + +## Selectors + +| Selector | Example | Description | +|----------|------------------------------------------|------------------------| +| Hostname | `http_challenge:hostname:p1.example.com` | The Subject's Hostname | + +## Security Considerations + +Generally, TCP ports are accessible to any user of the node. As a result, it is possible for non-agent code running on a node to attest to the SPIRE Server, allowing it to obtain any workload identity that the node is authorized to run. + +The `http_challenge` node attestor implements multiple features to mitigate the risk. + +Trust On First Use (or TOFU) is one such option. For any given node, attestation may occur only once when enabled. Subsequent attestation attempts will be rejected. + +With TOFU, it is still possible for non-agent code to complete node attestation before SPIRE Agent can, however this condition is easily and quickly detectable as SPIRE Agent will fail to start, and both SPIRE Agent and SPIRE Server will log the occurrence. Such cases should be investigated as possible security incidents. + +You also can require the port to be a trusted port that only trusted user such as root can open (port number < 1024). diff --git a/go.mod b/go.mod index ba1f6b8d45b..d29183ac261 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/spiffe/spire -go 1.22.3 +go 1.22.6 require ( cloud.google.com/go/iam v1.1.12 @@ -18,19 +18,19 @@ require ( github.com/Microsoft/go-winio v0.6.2 github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 github.com/aws/aws-sdk-go-v2 v1.30.3 - github.com/aws/aws-sdk-go-v2/config v1.27.18 - github.com/aws/aws-sdk-go-v2/credentials v1.17.18 - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 + github.com/aws/aws-sdk-go-v2/config v1.27.27 + github.com/aws/aws-sdk-go-v2/credentials v1.17.27 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.2 github.com/aws/aws-sdk-go-v2/service/acmpca v1.35.0 github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 github.com/aws/aws-sdk-go-v2/service/iam v1.34.1 - github.com/aws/aws-sdk-go-v2/service/kms v1.35.1 + github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 github.com/aws/aws-sdk-go-v2/service/organizations v1.30.2 github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.14.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 github.com/aws/smithy-go v1.20.3 github.com/blang/semver/v4 v4.0.0 github.com/cenkalti/backoff/v4 v4.3.0 @@ -66,9 +66,9 @@ require ( github.com/open-policy-agent/opa v0.67.0 github.com/prometheus/client_golang v1.19.1 github.com/shirou/gopsutil/v3 v3.24.5 - github.com/sigstore/cosign/v2 v2.2.4 + github.com/sigstore/cosign/v2 v2.4.0 github.com/sigstore/rekor v1.3.6 - github.com/sigstore/sigstore v1.8.7 + github.com/sigstore/sigstore v1.8.8 github.com/sirupsen/logrus v1.9.3 github.com/spiffe/go-spiffe/v2 v2.3.0 github.com/spiffe/spire-api-sdk v1.2.5-0.20240627195926-b5ac064f580b @@ -80,7 +80,7 @@ require ( golang.org/x/crypto v0.25.0 golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 golang.org/x/net v0.27.0 - golang.org/x/sync v0.7.0 + golang.org/x/sync v0.8.0 golang.org/x/sys v0.22.0 golang.org/x/time v0.5.0 google.golang.org/api v0.190.0 @@ -147,8 +147,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect @@ -208,7 +208,7 @@ require ( github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/certificate-transparency-go v1.1.8 // indirect + github.com/google/certificate-transparency-go v1.2.1 // indirect github.com/google/flatbuffers v23.5.26+incompatible // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/go-configfs-tsm v0.2.2 // indirect @@ -243,7 +243,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.8 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect @@ -275,15 +275,15 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pborman/uuid v1.2.1 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/posener/complete v1.2.3 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.51.1 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect @@ -295,7 +295,8 @@ require ( github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.2.0 // indirect - github.com/sigstore/fulcio v1.4.5 // indirect + github.com/sigstore/fulcio v1.5.1 // indirect + github.com/sigstore/protobuf-specs v0.3.2 // indirect github.com/sigstore/timestamp-authority v1.2.2 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -303,7 +304,7 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.18.2 // indirect + github.com/spf13/viper v1.19.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect @@ -316,7 +317,7 @@ require ( github.com/transparency-dev/merkle v0.0.2 // indirect github.com/twmb/murmur3 v1.1.8 // indirect github.com/vbatts/tar-split v0.11.5 // indirect - github.com/xanzy/go-gitlab v0.102.0 // indirect + github.com/xanzy/go-gitlab v0.107.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect @@ -330,12 +331,12 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/sdk v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - go.step.sm/crypto v0.44.2 // indirect + go.step.sm/crypto v0.51.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/mod v0.19.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf // indirect @@ -346,9 +347,9 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/release-utils v0.7.7 // indirect + sigs.k8s.io/release-utils v0.8.4 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 994df1b1a26..6c7c170a276 100644 --- a/go.sum +++ b/go.sum @@ -409,10 +409,10 @@ cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1V cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= -cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e h1:GwCVItFUPxwdsEYnlUcJ6PJxOjTeFFCKOh6QWg4oAzQ= -cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e/go.mod h1:ApHceQLLwcOkCEXM1+DyCXTHEJhNGDpJ2kmV6axsx24= -cuelang.org/go v0.8.1 h1:VFYsxIFSPY5KgSaH1jQ2GxHOrbu6Ga3kEI70yCZwnOg= -cuelang.org/go v0.8.1/go.mod h1:CoDbYolfMms4BhWUlhD+t5ORnihR7wvjcfgyO9lL5FI= +cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2 h1:BnG6pr9TTr6CYlrJznYUDj6V7xldD1W+1iXPum0wT/w= +cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2/go.mod h1:pK23AUVXuNzzTpfMCA06sxZGeVQ/75FdVtW249de9Uo= +cuelang.org/go v0.9.2 h1:pfNiry2PdRBr02G/aKm5k2vhzmqbAOoaB4WurmEbWvs= +cuelang.org/go v0.9.2/go.mod h1:qpAYsLOf7gTM1YdEg6cxh553uZ4q9ZDWlPbtZr9q1Wk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= @@ -563,18 +563,18 @@ github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.51.6 h1:Ld36dn9r7P9IjU8WZSaswQ8Y/XUCRpewim5980DwYiU= -github.com/aws/aws-sdk-go v1.51.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM= -github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk= -github.com/aws/aws-sdk-go-v2/config v1.27.18/go.mod h1:0xz6cgdX55+kmppvPm2IaKzIXOheGJhAufacPJaXZ7c= -github.com/aws/aws-sdk-go-v2/credentials v1.17.18 h1:D/ALDWqK4JdY3OFgA2thcPO1c9aYTT5STS/CvnkqY1c= -github.com/aws/aws-sdk-go-v2/credentials v1.17.18/go.mod h1:JuitCWq+F5QGUrmMPsk945rop6bB57jdscu+Glozdnc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzSClHZje/6JkPW5aZyEvrQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5/go.mod h1:gjvE2KBUgUQhcv89jqxrIxH9GaKs1JbZzWejj/DaHGA= +github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= +github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.2 h1:TFju6ZoqO3TnX0C42VmYW4TxNcUFfbV/3cnaOxbcc5Y= github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.2/go.mod h1:HLaNMGEhcO6GnJtrozRtluhCVM5/B/ZV5XHQ477uIgA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= @@ -603,8 +603,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrx github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg= -github.com/aws/aws-sdk-go-v2/service/kms v1.35.1 h1:0gP2OJJT6HM2BYltZ9x+A87OE8LJL96DXeAAdLv3t1M= -github.com/aws/aws-sdk-go-v2/service/kms v1.35.1/go.mod h1:hGONorZkQCfR5DW6l2xdy7zC8vfO0r9pJlwyg6gmGeo= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8= github.com/aws/aws-sdk-go-v2/service/organizations v1.30.2 h1:+tGF0JH2u4HwneqNFAKFHqENwfpBweKj67+LbwTKpqE= github.com/aws/aws-sdk-go-v2/service/organizations v1.30.2/go.mod h1:6wxO8s5wMumyNRsOgOgcIvqvF8rIf8Cj7Khhn/bFI0c= github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.14.0 h1:LoDKjG6X8Hj/Kiqmgpu/jW52GDTeToC6BehMbgHsZkg= @@ -613,12 +613,12 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALw github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.1 h1:ZoYRD8IJqPkzjBnpokiMNO6L/DQprtpVpD6k0YSaF5U= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.1/go.mod h1:GlRarZzIMl9VDi0mLQt+qQOuEkVFPnTkkjyugV1uVa8= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 h1:gEYM2GSpr4YNWc6hCd5nod4+d4kd9vWIAWrmGuLdlMw= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.11/go.mod h1:gVvwPdPNYehHSP9Rs7q27U1EU+3Or2ZpXvzAYJNh63w= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 h1:iXjh3uaH3vsVcnyZX7MqCoCfcyxIrVE9iOQruRaWPrQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5/go.mod h1:5ZXesEuy/QcO0WUnt+4sDkxhdXRHTu2yG0uCSH8B6os= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M= @@ -633,14 +633,16 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY= -github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE= -github.com/buildkite/agent/v3 v3.62.0 h1:yvzSjI8Lgifw883I8m9u8/L/Thxt4cLFd5aWPn3gg70= -github.com/buildkite/agent/v3 v3.62.0/go.mod h1:jN6SokGXrVNNIpI0BGQ+j5aWeI3gin8F+3zwA5Q6gqM= -github.com/buildkite/go-pipeline v0.3.2 h1:SW4EaXNwfjow7xDRPGgX0Rcx+dPj5C1kV9LKCLjWGtM= -github.com/buildkite/go-pipeline v0.3.2/go.mod h1:iY5jzs3Afc8yHg6KDUcu3EJVkfaUkd9x/v/OH98qyUA= -github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 h1:k6UDF1uPYOs0iy1HPeotNa155qXRWrzKnqAaGXHLZCE= -github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251/go.mod h1:gbPR1gPu9dB96mucYIR7T3B7p/78hRVSOuzIWLHK2Y4= +github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM= +github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY= +github.com/buildkite/agent/v3 v3.76.2 h1:SweFq3e0N20RikWsVeOXzTjfr0AoOskxm9c0bcNyI0E= +github.com/buildkite/agent/v3 v3.76.2/go.mod h1:9ffbmJD7d7C/nOcElj6Qm+uIj1QoYh3NNvka4rkKkss= +github.com/buildkite/go-pipeline v0.10.0 h1:EDffu+LfMY2k5u+iEdo6Jn3obGKsrL5wicc1O/yFeRs= +github.com/buildkite/go-pipeline v0.10.0/go.mod h1:eMH1kiav5VeiTiu0Mk2/M7nZhKyFeL4iGj7Y7rj4f3w= +github.com/buildkite/interpolate v0.1.3 h1:OFEhqji1rNTRg0u9DsSodg63sjJQEb1uWbENq9fUOBM= +github.com/buildkite/interpolate v0.1.3/go.mod h1:UNVe6A+UfiBNKbhAySrBbZFZFxQ+DXr9nWen6WVt/A8= +github.com/buildkite/roko v1.2.0 h1:hbNURz//dQqNl6Eo9awjQOVOZwSDJ8VEbBDxSfT9rGQ= +github.com/buildkite/roko v1.2.0/go.mod h1:23R9e6nHxgedznkwwfmqZ6+0VJZJZ2Sg/uVcp2cP46I= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= @@ -838,8 +840,8 @@ github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3Bum github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-piv/piv-go v1.11.0 h1:5vAaCdRTFSIW4PeqMbnsDlUZ7odMYWnHBDGdmtU/Zhg= github.com/go-piv/piv-go v1.11.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM= -github.com/go-rod/rod v0.116.1 h1:BDMZY3qm/14SmvHBV7DoFUhXeJ2MbUYgumQ88b+v2WE= -github.com/go-rod/rod v0.116.1/go.mod h1:3Ash9fYwznqz9S1uLQgQRStur4fCXjoxxGW+ym6TYjU= +github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= +github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= @@ -918,8 +920,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/certificate-transparency-go v1.1.8 h1:LGYKkgZF7satzgTak9R4yzfJXEeYVAjV6/EAEJOf1to= -github.com/google/certificate-transparency-go v1.1.8/go.mod h1:bV/o8r0TBKRf1X//iiiSgWrvII4d7/8OiA+3vG26gI8= +github.com/google/certificate-transparency-go v1.2.1 h1:4iW/NwzqOqYEEoCBEFP+jPbBXbLqMpq3CifMyOnDUME= +github.com/google/certificate-transparency-go v1.2.1/go.mod h1:bvn/ytAccv+I6+DGkqpvSsEdiVGramgaSC6RD3tEmeE= github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg= github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= @@ -1119,8 +1121,8 @@ github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++ github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= -github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls= -github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k= +github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg= +github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8= github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -1149,8 +1151,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -1290,8 +1292,8 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1325,15 +1327,15 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw= -github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/protocolbuffers/txtpbfmt v0.0.0-20231025115547-084445ff1adf h1:014O62zIzQwvoD7Ekj3ePDF5bv9Xxy0w6AZk0qYbjUk= github.com/protocolbuffers/txtpbfmt v0.0.0-20231025115547-084445ff1adf/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= @@ -1369,22 +1371,26 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sigstore/cosign/v2 v2.2.4 h1:iY4vtEacmu2hkNj1Fh+8EBqBwKs2DHM27/lbNWDFJro= -github.com/sigstore/cosign/v2 v2.2.4/go.mod h1:JZlRD2uaEjVAvZ1XJ3QkkZJhTqSDVtLaet+C/TMR81Y= -github.com/sigstore/fulcio v1.4.5 h1:WWNnrOknD0DbruuZWCbN+86WRROpEl3Xts+WT2Ek1yc= -github.com/sigstore/fulcio v1.4.5/go.mod h1:oz3Qwlma8dWcSS/IENR/6SjbW4ipN0cxpRVfgdsjMU8= +github.com/sigstore/cosign/v2 v2.4.0 h1:2NdidNgClg+oXr/fDIr37E/BE6j00gqgUhSiBK2kjSQ= +github.com/sigstore/cosign/v2 v2.4.0/go.mod h1:j+fH1DCUkcn92qp6ezDj4JbGMri6eG1nLJC+hs64rvc= +github.com/sigstore/fulcio v1.5.1 h1:Iasy1zfNjaq8BV4S8o6pXspLDU28PQC2z07GmOu9zpM= +github.com/sigstore/fulcio v1.5.1/go.mod h1:W1A/UHrTopy1IBZPMtHmxg7GPYAu+vt5dRXM3W6yjPo= +github.com/sigstore/protobuf-specs v0.3.2 h1:nCVARCN+fHjlNCk3ThNXwrZRqIommIeNKWwQvORuRQo= +github.com/sigstore/protobuf-specs v0.3.2/go.mod h1:RZ0uOdJR4OB3tLQeAyWoJFbNCBFrPQdcokntde4zRBA= github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8= github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc= -github.com/sigstore/sigstore v1.8.7 h1:L7/zKauHTg0d0Hukx7qlR4nifh6T6O6UIt9JBwAmTIg= -github.com/sigstore/sigstore v1.8.7/go.mod h1:MPiQ/NIV034Fc3Kk2IX9/XmBQdK60wfmpvgK9Z1UjRA= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3 h1:LTfPadUAo+PDRUbbdqbeSl2OuoFQwUFTnJ4stu+nwWw= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3/go.mod h1:QV/Lxlxm0POyhfyBtIbTWxNeF18clMlkkyL9mu45y18= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3 h1:xgbPRCr2npmmsuVVteJqi/ERw9+I13Wou7kq0Yk4D8g= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3/go.mod h1:G4+I83FILPX6MtnoaUdmv/bRGEVtR3JdLeJa/kXdk/0= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.3 h1:vDl2fqPT0h3D/k6NZPlqnKFd1tz3335wm39qjvpZNJc= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.3/go.mod h1:9uOJXbXEXj+M6QjMKH5PaL5WDMu43rHfbIMgXzA8eKI= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.3 h1:h9G8j+Ds21zqqulDbA/R/ft64oQQIyp8S7wJYABYSlg= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.3/go.mod h1:zgCeHOuqF6k7A7TTEvftcA9V3FRzB7mrPtHOhXAQBnc= +github.com/sigstore/sigstore v1.8.8 h1:B6ZQPBKK7Z7tO3bjLNnlCMG+H66tO4E/+qAphX8T/hg= +github.com/sigstore/sigstore v1.8.8/go.mod h1:GW0GgJSCTBJY3fUOuGDHeFWcD++c4G8Y9K015pwcpDI= +github.com/sigstore/sigstore-go v0.5.1 h1:5IhKvtjlQBeLnjKkzMELNG4tIBf+xXQkDzhLV77+/8Y= +github.com/sigstore/sigstore-go v0.5.1/go.mod h1:TuOfV7THHqiDaUHuJ5+QN23RP/YoKmsbwJpY+aaYPN0= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8 h1:2zHmUvaYCwV6LVeTo+OAkTm8ykOGzA9uFlAjwDPAUWM= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8/go.mod h1:OEhheBplZinUsm7W9BupafztVZV3ldkAxEHbpAeC0Pk= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.8 h1:RKk4Z+qMaLORUdT7zntwMqKiYAej1VQlCswg0S7xNSY= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.8/go.mod h1:dMJdlBWKHMu2xf0wIKpbo7+QfG+RzVkBB3nHP8EMM5o= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8 h1:89Xtxj8oqZt3UlSpCP4wApFvnQ2Z/dgowW5QOVhQigI= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8/go.mod h1:Wa4xn/H3pU/yW/6tHiMXTpObBtBSGC5q29KYFEPKN6o= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.8 h1:Zte3Oogkd8m+nu2oK3yHtGmN++TZWh2Lm6q2iSprT1M= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.8/go.mod h1:j00crVw6ki4/WViXflw0zWgNALrAzZT+GbIK8v7Xlz4= github.com/sigstore/timestamp-authority v1.2.2 h1:X4qyutnCQqJ0apMewFyx+3t7Tws00JQ/JonBiu3QvLE= github.com/sigstore/timestamp-authority v1.2.2/go.mod h1:nEah4Eq4wpliDjlY342rXclGSO7Kb9hoRrl9tqLW13A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1415,8 +1421,8 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/spiffe/go-spiffe/v2 v2.1.6/go.mod h1:eVDqm9xFvyqao6C+eQensb9ZPkyNEeaUbqbBpOhBnNk= github.com/spiffe/go-spiffe/v2 v2.3.0 h1:g2jYNb/PDMB8I7mBGL2Zuq/Ur6hUhoroxGQFyD6tTj8= github.com/spiffe/go-spiffe/v2 v2.3.0/go.mod h1:Oxsaio7DBgSNqhAO9i/9tLClaVlfRok7zvJnTV8ZyIY= @@ -1455,6 +1461,8 @@ github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gt github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= +github.com/theupdateframework/go-tuf/v2 v2.0.0 h1:rD8d9RotYBprZVgC+9oyTZ5MmawepnTSTqoDuxjWgbs= +github.com/theupdateframework/go-tuf/v2 v2.0.0/go.mod h1:baB22nBHeHBCeuGZcIlctNq4P61PcOdyARlplg5xmLA= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= @@ -1476,8 +1484,8 @@ github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXV github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= -github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4= -github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= +github.com/xanzy/go-gitlab v0.107.0 h1:P2CT9Uy9yN9lJo3FLxpMZ4xj6uWcpnigXsjvqJ6nd2Y= +github.com/xanzy/go-gitlab v0.107.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -1492,8 +1500,8 @@ github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q= github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg= github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= -github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak= -github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= +github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU= +github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1540,8 +1548,8 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.step.sm/crypto v0.44.2 h1:t3p3uQ7raP2jp2ha9P6xkQF85TJZh+87xmjSLaib+jk= -go.step.sm/crypto v0.44.2/go.mod h1:x1439EnFhadzhkuaGX7sz03LEMQ+jV4gRamf5LCZJQQ= +go.step.sm/crypto v0.51.1 h1:ktUg/2hetEMiBAqgz502ktZDGoDoGrcHFg3XpkmkvvA= +go.step.sm/crypto v0.51.1/go.mod h1:PdrhttNU/tG9/YsVd4fdlysBN+UV503p0o2irFZQlAw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -1621,8 +1629,8 @@ golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1718,8 +1726,8 @@ golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1735,8 +1743,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1937,8 +1945,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2249,8 +2257,8 @@ k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7F k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/mount-utils v0.30.3 h1:8Z3wSW5+GSvGNtlDhtoZrBCKLMIf5z/9tf8pie+G06s= k8s.io/mount-utils v0.30.3/go.mod h1:9sCVmwGLcV1MPvbZ+rToMDnl1QcGozy+jBPd0MsQLIo= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= @@ -2258,8 +2266,8 @@ sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHv sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/release-utils v0.7.7 h1:JKDOvhCk6zW8ipEOkpTGDH/mW3TI+XqtPp16aaQ79FU= -sigs.k8s.io/release-utils v0.7.7/go.mod h1:iU7DGVNi3umZJ8q6aHyUFzsDUIaYwNnNKGHo3YE5E3s= +sigs.k8s.io/release-utils v0.8.4 h1:4QVr3UgbyY/d9p74LBhg0njSVQofUsAZqYOzVZBhdBw= +sigs.k8s.io/release-utils v0.8.4/go.mod h1:m1bHfscTemQp+z+pLCZnkXih9n0+WukIUU70n6nFnU0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/pkg/agent/catalog/nodeattestor.go b/pkg/agent/catalog/nodeattestor.go index eee9a0ddda6..5db0ca8d264 100644 --- a/pkg/agent/catalog/nodeattestor.go +++ b/pkg/agent/catalog/nodeattestor.go @@ -5,6 +5,7 @@ import ( "github.com/spiffe/spire/pkg/agent/plugin/nodeattestor/awsiid" "github.com/spiffe/spire/pkg/agent/plugin/nodeattestor/azuremsi" "github.com/spiffe/spire/pkg/agent/plugin/nodeattestor/gcpiit" + "github.com/spiffe/spire/pkg/agent/plugin/nodeattestor/httpchallenge" "github.com/spiffe/spire/pkg/agent/plugin/nodeattestor/jointoken" "github.com/spiffe/spire/pkg/agent/plugin/nodeattestor/k8spsat" "github.com/spiffe/spire/pkg/agent/plugin/nodeattestor/k8ssat" @@ -37,6 +38,7 @@ func (repo *nodeAttestorRepository) BuiltIns() []catalog.BuiltIn { awsiid.BuiltIn(), azuremsi.BuiltIn(), gcpiit.BuiltIn(), + httpchallenge.BuiltIn(), jointoken.BuiltIn(), k8spsat.BuiltIn(), k8ssat.BuiltIn(), diff --git a/pkg/agent/plugin/nodeattestor/httpchallenge/httpchallenge.go b/pkg/agent/plugin/nodeattestor/httpchallenge/httpchallenge.go new file mode 100644 index 00000000000..9f1b69754a3 --- /dev/null +++ b/pkg/agent/plugin/nodeattestor/httpchallenge/httpchallenge.go @@ -0,0 +1,234 @@ +package httpchallenge + +import ( + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "os" + "sync" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/hcl" + nodeattestorv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/agent/nodeattestor/v1" + configv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/service/common/config/v1" + "github.com/spiffe/spire/pkg/common/catalog" + "github.com/spiffe/spire/pkg/common/plugin/httpchallenge" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + pluginName = "http_challenge" +) + +func BuiltIn() catalog.BuiltIn { + return builtin(New()) +} + +func BuiltInWithHostname(hostname string) catalog.BuiltIn { + plugin := New() + plugin.hostname = hostname + return builtin(plugin) +} + +func builtin(p *Plugin) catalog.BuiltIn { + return catalog.MakeBuiltIn(pluginName, + nodeattestorv1.NodeAttestorPluginServer(p), + configv1.ConfigServiceServer(p)) +} + +type configData struct { + port int + advertisedPort int + hostName string + agentName string +} + +type Config struct { + HostName string `hcl:"hostname"` + AgentName string `hcl:"agentname"` + Port int `hcl:"port"` + AdvertisedPort int `hcl:"advertised_port"` +} + +type Plugin struct { + nodeattestorv1.UnsafeNodeAttestorServer + configv1.UnsafeConfigServer + + m sync.Mutex + c *Config + + log hclog.Logger + + hostname string +} + +func New() *Plugin { + return &Plugin{} +} + +func (p *Plugin) AidAttestation(stream nodeattestorv1.NodeAttestor_AidAttestationServer) (err error) { + data, err := p.loadConfigData() + if err != nil { + return err + } + + ctx := stream.Context() + + port := data.port + + l, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + return status.Errorf(codes.Internal, "could not listen on port %d: %v", port, err) + } + defer l.Close() + + advertisedPort := data.advertisedPort + if advertisedPort == 0 { + advertisedPort = l.Addr().(*net.TCPAddr).Port + } + + attestationPayload, err := json.Marshal(httpchallenge.AttestationData{ + HostName: data.hostName, + AgentName: data.agentName, + Port: advertisedPort, + }) + if err != nil { + return status.Errorf(codes.Internal, "unable to marshal attestation data: %v", err) + } + + // send the attestation data back to the agent + if err := stream.Send(&nodeattestorv1.PayloadOrChallengeResponse{ + Data: &nodeattestorv1.PayloadOrChallengeResponse_Payload{ + Payload: attestationPayload, + }, + }); err != nil { + return err + } + + // receive challenge + resp, err := stream.Recv() + if err != nil { + return err + } + + challenge := new(httpchallenge.Challenge) + if err := json.Unmarshal(resp.Challenge, challenge); err != nil { + return status.Errorf(codes.Internal, "unable to unmarshal challenge: %v", err) + } + + // due to https://github.com/spiffe/spire/blob/8f9fa036e182a2fab968e03cd25a7fdb2d8c88bb/pkg/agent/plugin/nodeattestor/v1.go#L63, we must respond with a non blank challenge response + responseBytes := []byte{'\n'} + if err := stream.Send(&nodeattestorv1.PayloadOrChallengeResponse{ + Data: &nodeattestorv1.PayloadOrChallengeResponse_ChallengeResponse{ + ChallengeResponse: responseBytes, + }, + }); err != nil { + return err + } + + err = p.serveNonce(ctx, l, data.agentName, challenge.Nonce) + if err != nil { + return status.Errorf(codes.Internal, "failed to start webserver: %v", err) + } + return nil +} + +func (p *Plugin) Configure(_ context.Context, req *configv1.ConfigureRequest) (*configv1.ConfigureResponse, error) { + // Parse HCL config payload into config struct + config := new(Config) + if err := hcl.Decode(config, req.HclConfiguration); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "unable to decode configuration: %v", err) + } + + // Make sure the configuration produces valid data + if _, err := loadConfigData(p.hostname, config); err != nil { + return nil, err + } + + p.setConfig(config) + + return &configv1.ConfigureResponse{}, nil +} + +func (p *Plugin) serveNonce(ctx context.Context, l net.Listener, agentName string, nonce string) (err error) { + h := http.NewServeMux() + s := &http.Server{ + Handler: h, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + } + path := fmt.Sprintf("/.well-known/spiffe/nodeattestor/http_challenge/%s/challenge", agentName) + p.log.Debug("Setting up nonce handler", "path", path) + h.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, nonce) + }) + + go func() { + <-ctx.Done() + _ = s.Shutdown(context.Background()) + }() + + err = s.Serve(l) + if err == http.ErrServerClosed { + return nil + } + return err +} + +// SetLogger sets this plugin's logger +func (p *Plugin) SetLogger(log hclog.Logger) { + p.log = log +} + +func (p *Plugin) getConfig() *Config { + p.m.Lock() + defer p.m.Unlock() + return p.c +} + +func (p *Plugin) setConfig(c *Config) { + p.m.Lock() + defer p.m.Unlock() + p.c = c +} + +func (p *Plugin) loadConfigData() (*configData, error) { + config := p.getConfig() + if config == nil { + return nil, status.Error(codes.FailedPrecondition, "not configured") + } + return loadConfigData(p.hostname, config) +} + +func loadConfigData(hostname string, config *Config) (*configData, error) { + if config.HostName == "" { + if hostname != "" { + config.HostName = hostname + } else { + var err error + config.HostName, err = os.Hostname() + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "unable to fetch hostname: %v", err) + } + } + } + var agentName = "default" + if config.AgentName != "" { + agentName = config.AgentName + } + + if config.AdvertisedPort == 0 { + config.AdvertisedPort = config.Port + } + + return &configData{ + port: config.Port, + advertisedPort: config.AdvertisedPort, + hostName: config.HostName, + agentName: agentName, + }, nil +} diff --git a/pkg/agent/plugin/nodeattestor/httpchallenge/httpchallenge_test.go b/pkg/agent/plugin/nodeattestor/httpchallenge/httpchallenge_test.go new file mode 100644 index 00000000000..9f1fa144d3c --- /dev/null +++ b/pkg/agent/plugin/nodeattestor/httpchallenge/httpchallenge_test.go @@ -0,0 +1,211 @@ +//go:build !darwin + +package httpchallenge_test + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net" + "net/http" + "testing" + + configv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/service/common/config/v1" + "github.com/spiffe/spire/pkg/agent/plugin/nodeattestor" + "github.com/spiffe/spire/pkg/agent/plugin/nodeattestor/httpchallenge" + nodeattestortest "github.com/spiffe/spire/pkg/agent/plugin/nodeattestor/test" + common_httpchallenge "github.com/spiffe/spire/pkg/common/plugin/httpchallenge" + "github.com/spiffe/spire/test/plugintest" + "github.com/stretchr/testify/require" +) + +var ( + streamBuilder = nodeattestortest.ServerStream("http_challenge") +) + +func TestConfigureCommon(t *testing.T) { + tests := []struct { + name string + hclConf string + expErr string + }{ + { + name: "Configure fails if receives wrong HCL configuration", + hclConf: "not HCL conf", + expErr: "rpc error: code = InvalidArgument desc = unable to decode configuration", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + plugin := httpchallenge.New() + + resp, err := plugin.Configure(context.Background(), &configv1.ConfigureRequest{HclConfiguration: tt.hclConf}) + if tt.expErr != "" { + require.Contains(t, err.Error(), tt.expErr) + require.Nil(t, resp) + return + } + + require.NoError(t, err) + require.NotNil(t, resp) + }) + } +} + +func TestAidAttestationFailures(t *testing.T) { + tests := []struct { + name string + config string + expErr string + serverStream nodeattestor.ServerStream + }{ + { + name: "AidAttestation fails if server does not sends a challenge", + config: "", + expErr: "the error", + serverStream: streamBuilder.FailAndBuild(errors.New("the error")), + }, + { + name: "AidAttestation fails if agent cannot unmarshal server challenge", + config: "", + expErr: "rpc error: code = Internal desc = nodeattestor(http_challenge): unable to unmarshal challenge: invalid character 'o' in literal null (expecting 'u')", + serverStream: streamBuilder.IgnoreThenChallenge([]byte("not-a-challenge")).Build(), + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + var err error + p := loadAndConfigurePlugin(t, tt.config) + + err = p.Attest(context.Background(), tt.serverStream) + if tt.expErr != "" { + require.Contains(t, err.Error(), tt.expErr) + return + } + require.NoError(t, err) + }) + } +} + +func TestAidAttestationSucceeds(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + port := l.Addr().(*net.TCPAddr).Port + defer l.Close() + + tests := []struct { + name string + config string + attestationData common_httpchallenge.AttestationData + serverStream func(attestationData *common_httpchallenge.AttestationData, challenge []byte, expectPayload []byte, challengeobj *common_httpchallenge.Challenge, port int) nodeattestor.ServerStream + }{ + { + name: "Check for random port", + config: "", + attestationData: common_httpchallenge.AttestationData{ + HostName: "spire-dev", + AgentName: "default", + }, + serverStream: func(attestationData *common_httpchallenge.AttestationData, challenge []byte, expectPayload []byte, challengeobj *common_httpchallenge.Challenge, port int) nodeattestor.ServerStream { + return streamBuilder. + Handle(func(challenge []byte) ([]byte, error) { + attestationData := new(common_httpchallenge.AttestationData) + if err := json.Unmarshal(challenge, attestationData); err != nil { + return nil, err + } + if attestationData.Port == port { + return nil, errors.New("random port failed") + } + return nil, nil + }).Build() + }, + }, + { + name: "Check for advertised port", + config: fmt.Sprintf("advertised_port = %d", port), + attestationData: common_httpchallenge.AttestationData{ + HostName: "spire-dev", + AgentName: "default", + }, + serverStream: func(attestationData *common_httpchallenge.AttestationData, challenge []byte, expectPayload []byte, challengeobj *common_httpchallenge.Challenge, port int) nodeattestor.ServerStream { + return streamBuilder. + Handle(func(challenge []byte) ([]byte, error) { + attestationData := new(common_httpchallenge.AttestationData) + if err := json.Unmarshal(challenge, attestationData); err != nil { + return nil, err + } + if attestationData.Port != port { + return nil, errors.New("advertised port failed") + } + return nil, nil + }).Build() + }, + }, + { + name: "Test with defaults except port", + config: "port=9999", + attestationData: common_httpchallenge.AttestationData{ + HostName: "localhost", + AgentName: "default", + Port: 9999, + }, + serverStream: func(attestationData *common_httpchallenge.AttestationData, challenge []byte, expectPayload []byte, challengeobj *common_httpchallenge.Challenge, port int) nodeattestor.ServerStream { + return streamBuilder.IgnoreThenChallenge(challenge). + Handle(func(challengeResponse []byte) ([]byte, error) { + err := common_httpchallenge.VerifyChallenge(context.Background(), http.DefaultClient, attestationData, challengeobj) + return nil, err + }).Build() + }, + }, + { + name: "Full test with all the settings", + config: "hostname=\"localhost\"\nagentname=\"test\"\nport=9999\nadvertised_port=9999", + attestationData: common_httpchallenge.AttestationData{ + HostName: "localhost", + AgentName: "test", + Port: 9999, + }, + serverStream: func(attestationData *common_httpchallenge.AttestationData, challenge []byte, expectPayload []byte, challengeobj *common_httpchallenge.Challenge, port int) nodeattestor.ServerStream { + return streamBuilder.ExpectThenChallenge(expectPayload, challenge). + Handle(func(challengeResponse []byte) ([]byte, error) { + err := common_httpchallenge.VerifyChallenge(context.Background(), http.DefaultClient, attestationData, challengeobj) + return nil, err + }).Build() + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + var err error + expectPayload, err := json.Marshal(&tt.attestationData) + require.NoError(t, err) + + challengeobj, err := common_httpchallenge.GenerateChallenge("") + require.NoError(t, err) + + challenge, err := json.Marshal(challengeobj) + require.NoError(t, err) + + p := loadAndConfigurePlugin(t, tt.config) + + err = p.Attest(context.Background(), tt.serverStream(&tt.attestationData, challenge, expectPayload, challengeobj, port)) + require.NoError(t, err) + }) + } +} + +func loadAndConfigurePlugin(t *testing.T, config string) nodeattestor.NodeAttestor { + return loadPlugin(t, plugintest.Configure(config)) +} + +func loadPlugin(t *testing.T, options ...plugintest.Option) nodeattestor.NodeAttestor { + na := new(nodeattestor.V1) + plugintest.Load(t, httpchallenge.BuiltInWithHostname("localhost"), na, options...) + return na +} diff --git a/pkg/common/plugin/httpchallenge/httpchallenge.go b/pkg/common/plugin/httpchallenge/httpchallenge.go new file mode 100644 index 00000000000..2f3e7e36f8c --- /dev/null +++ b/pkg/common/plugin/httpchallenge/httpchallenge.go @@ -0,0 +1,115 @@ +package httpchallenge + +import ( + "context" + "crypto/rand" + "encoding/base64" + "fmt" + "io" + "net" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/spiffe/spire/pkg/common/idutil" +) + +const ( + nonceLen = 32 + + // PluginName for http based attestor + PluginName = "http_challenge" +) + +type AttestationData struct { + HostName string `json:"hostname"` + AgentName string `json:"agentname"` + Port int `json:"port"` +} + +type Challenge struct { + Nonce string `json:"nonce"` +} + +type Response struct { +} + +func GenerateChallenge(forceNonce string) (*Challenge, error) { + nonce := forceNonce + if nonce == "" { + var err error + nonce, err = generateNonce() + if err != nil { + return nil, err + } + } + return &Challenge{Nonce: nonce}, nil +} + +func CalculateResponse(_ *Challenge) (*Response, error) { + return &Response{}, nil +} + +func VerifyChallenge(ctx context.Context, client *http.Client, attestationData *AttestationData, challenge *Challenge) error { + if attestationData.HostName == "" { + return fmt.Errorf("hostname must be set") + } + if attestationData.AgentName == "" { + return fmt.Errorf("agentname must be set") + } + if attestationData.Port <= 0 { + return fmt.Errorf("port is invalid") + } + if strings.Contains(attestationData.HostName, "/") { + return fmt.Errorf("hostname can not contain a slash") + } + if strings.Contains(attestationData.HostName, ":") { + return fmt.Errorf("hostname can not contain a colon") + } + if strings.Contains(attestationData.AgentName, ".") { + return fmt.Errorf("agentname can not contain a dot") + } + turl := url.URL{ + Scheme: "http", + Host: net.JoinHostPort(attestationData.HostName, strconv.Itoa(attestationData.Port)), + Path: fmt.Sprintf("/.well-known/spiffe/nodeattestor/http_challenge/%s/challenge", attestationData.AgentName), + } + + req, err := http.NewRequestWithContext(ctx, "GET", turl.String(), nil) + if err != nil { + return err + } + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + body, err := io.ReadAll(io.LimitReader(resp.Body, 64)) + if err != nil { + return err + } + nonce := strings.TrimSpace(string(body)) + if nonce != challenge.Nonce { + return fmt.Errorf("expected nonce %q but got %q", challenge.Nonce, body) + } + return nil +} + +// MakeAgentID creates an agent ID +func MakeAgentID(td spiffeid.TrustDomain, hostName string) (spiffeid.ID, error) { + agentPath := fmt.Sprintf("/http_challenge/%s", hostName) + + return idutil.AgentID(td, agentPath) +} + +func generateNonce() (string, error) { + b := make([]byte, nonceLen) + if _, err := rand.Read(b); err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(b), nil +} diff --git a/pkg/common/plugin/httpchallenge/httpchallenge_test.go b/pkg/common/plugin/httpchallenge/httpchallenge_test.go new file mode 100644 index 00000000000..0ec27f320ad --- /dev/null +++ b/pkg/common/plugin/httpchallenge/httpchallenge_test.go @@ -0,0 +1,100 @@ +package httpchallenge + +import ( + "context" + "net" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestValidateChallenge(t *testing.T) { + tests := []struct { + desc string + hostName string + agentName string + nonce string + testNonce string + expectErr string + }{ + { + desc: "bad hostName", + hostName: "foo/bar", + agentName: "ok", + nonce: "1234", + testNonce: "1234", + expectErr: "hostname can not contain a slash", + }, + { + desc: "bad hostName", + hostName: "foo:bar", + agentName: "ok", + nonce: "1234", + testNonce: "1234", + expectErr: "hostname can not contain a colon", + }, + { + desc: "bad agentName", + hostName: "foo.bar", + agentName: "not.ok", + nonce: "1234", + testNonce: "1234", + expectErr: "agentname can not contain a dot", + }, + { + desc: "fail nonce", + hostName: "foo.bar", + agentName: "ok", + nonce: "1234", + testNonce: "1235", + expectErr: "expected nonce \"1235\" but got \"1234\"", + }, + { + desc: "success", + hostName: "foo.bar", + agentName: "ok", + nonce: "1234", + testNonce: "1234", + expectErr: "", + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + ad := &AttestationData{ + HostName: tt.hostName, + AgentName: tt.agentName, + Port: 80, + } + c := &Challenge{ + Nonce: tt.testNonce, + } + + testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + _, err := res.Write([]byte(tt.nonce)) + require.NoError(t, err) + })) + defer func() { testServer.Close() }() + + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + if addr == "foo.bar:80" { + addr = strings.TrimPrefix(testServer.URL, "http://") + } + dialer := &net.Dialer{} + return dialer.DialContext(ctx, network, addr) + } + + err := VerifyChallenge(context.Background(), &http.Client{Transport: transport}, ad, c) + if tt.expectErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectErr) + return + } + require.NoError(t, err) + }) + } +} diff --git a/pkg/server/catalog/nodeattestor.go b/pkg/server/catalog/nodeattestor.go index 1340d5b3257..fe743889dcb 100644 --- a/pkg/server/catalog/nodeattestor.go +++ b/pkg/server/catalog/nodeattestor.go @@ -6,6 +6,7 @@ import ( "github.com/spiffe/spire/pkg/server/plugin/nodeattestor/awsiid" "github.com/spiffe/spire/pkg/server/plugin/nodeattestor/azuremsi" "github.com/spiffe/spire/pkg/server/plugin/nodeattestor/gcpiit" + "github.com/spiffe/spire/pkg/server/plugin/nodeattestor/httpchallenge" "github.com/spiffe/spire/pkg/server/plugin/nodeattestor/jointoken" "github.com/spiffe/spire/pkg/server/plugin/nodeattestor/k8spsat" "github.com/spiffe/spire/pkg/server/plugin/nodeattestor/k8ssat" @@ -37,6 +38,7 @@ func (repo *nodeAttestorRepository) BuiltIns() []catalog.BuiltIn { awsiid.BuiltIn(), azuremsi.BuiltIn(), gcpiit.BuiltIn(), + httpchallenge.BuiltIn(), jointoken.BuiltIn(), k8spsat.BuiltIn(), k8ssat.BuiltIn(), diff --git a/pkg/server/plugin/nodeattestor/httpchallenge/httpchallenge.go b/pkg/server/plugin/nodeattestor/httpchallenge/httpchallenge.go new file mode 100644 index 00000000000..c4ba141a42a --- /dev/null +++ b/pkg/server/plugin/nodeattestor/httpchallenge/httpchallenge.go @@ -0,0 +1,288 @@ +package httpchallenge + +import ( + "context" + "encoding/json" + "net/http" + "regexp" + "sync" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/hcl" + "github.com/spiffe/go-spiffe/v2/spiffeid" + nodeattestorv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/server/nodeattestor/v1" + configv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/service/common/config/v1" + "github.com/spiffe/spire/pkg/common/catalog" + "github.com/spiffe/spire/pkg/common/plugin/httpchallenge" + nodeattestorbase "github.com/spiffe/spire/pkg/server/plugin/nodeattestor/base" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + pluginName = "http_challenge" +) + +var ( + agentNamePattern = regexp.MustCompile("^[a-zA-z]+[a-zA-Z0-9-]$") +) + +func BuiltIn() catalog.BuiltIn { + return builtin(New()) +} + +func BuiltInTesting(client *http.Client, forceNonce string) catalog.BuiltIn { + plugin := New() + plugin.client = client + plugin.forceNonce = forceNonce + return builtin(plugin) +} + +func builtin(p *Plugin) catalog.BuiltIn { + return catalog.MakeBuiltIn(pluginName, + nodeattestorv1.NodeAttestorPluginServer(p), + configv1.ConfigServiceServer(p), + ) +} + +type configuration struct { + trustDomain spiffeid.TrustDomain + requiredPort *int + allowNonRootPorts bool + dnsPatterns []*regexp.Regexp + tofu bool +} + +type Config struct { + AllowedDNSPatterns []string `hcl:"allowed_dns_patterns"` + RequiredPort *int `hcl:"required_port"` + AllowNonRootPorts *bool `hcl:"allow_non_root_ports"` + TOFU *bool `hcl:"tofu"` +} + +type Plugin struct { + nodeattestorbase.Base + nodeattestorv1.UnsafeNodeAttestorServer + configv1.UnsafeConfigServer + + m sync.Mutex + config *configuration + + log hclog.Logger + + client *http.Client + forceNonce string +} + +func New() *Plugin { + return &Plugin{ + client: http.DefaultClient, + } +} + +func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error { + req, err := stream.Recv() + if err != nil { + return err + } + + config, err := p.getConfig() + if err != nil { + return err + } + + payload := req.GetPayload() + if payload == nil { + return status.Error(codes.InvalidArgument, "missing attestation payload") + } + + attestationData := new(httpchallenge.AttestationData) + if err := json.Unmarshal(payload, attestationData); err != nil { + return status.Errorf(codes.InvalidArgument, "failed to unmarshal data: %v", err) + } + + if config.requiredPort != nil && attestationData.Port != *config.requiredPort { + return status.Errorf(codes.InvalidArgument, "port %d is not allowed to be used by this server", attestationData.Port) + } + if (!config.allowNonRootPorts) && attestationData.Port >= 1024 { + return status.Errorf(codes.InvalidArgument, "port %d is not allowed to be >= 1024", attestationData.Port) + } + + if err = validateAgentName(attestationData.AgentName); err != nil { + return err + } + + if err = validateHostName(attestationData.HostName, config.dnsPatterns); err != nil { + return err + } + + challenge, err := httpchallenge.GenerateChallenge(p.forceNonce) + if err != nil { + return status.Errorf(codes.Internal, "unable to generate challenge: %v", err) + } + + challengeBytes, err := json.Marshal(challenge) + if err != nil { + return status.Errorf(codes.Internal, "unable to marshal challenge: %v", err) + } + + if err := stream.Send(&nodeattestorv1.AttestResponse{ + Response: &nodeattestorv1.AttestResponse_Challenge{ + Challenge: challengeBytes, + }, + }); err != nil { + return err + } + + // receive the response. We dont really care what it is but the plugin system requiries it. + _, err = stream.Recv() + if err != nil { + return err + } + + p.log.Debug("Verifying challenge") + + timeoutctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if err := httpchallenge.VerifyChallenge(timeoutctx, p.client, attestationData, challenge); err != nil { + return status.Errorf(codes.PermissionDenied, "challenge verification failed: %v", err) + } + + spiffeid, err := httpchallenge.MakeAgentID(config.trustDomain, attestationData.HostName) + if err != nil { + return status.Errorf(codes.Internal, "failed to make spiffe id: %v", err) + } + + if config.tofu { + if err := p.AssessTOFU(stream.Context(), spiffeid.String(), p.log); err != nil { + return err + } + } + + return stream.Send(&nodeattestorv1.AttestResponse{ + Response: &nodeattestorv1.AttestResponse_AgentAttributes{ + AgentAttributes: &nodeattestorv1.AgentAttributes{ + SpiffeId: spiffeid.String(), + SelectorValues: buildSelectorValues(attestationData.HostName), + CanReattest: !config.tofu, + }, + }, + }) +} + +func (p *Plugin) Configure(_ context.Context, req *configv1.ConfigureRequest) (*configv1.ConfigureResponse, error) { + hclConfig := new(Config) + if err := hcl.Decode(hclConfig, req.HclConfiguration); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "unable to decode configuration: %v", err) + } + + if req.CoreConfiguration == nil { + return nil, status.Error(codes.InvalidArgument, "core configuration is required") + } + + if req.CoreConfiguration.TrustDomain == "" { + return nil, status.Error(codes.InvalidArgument, "trust_domain is required") + } + + trustDomain, err := spiffeid.TrustDomainFromString(req.CoreConfiguration.TrustDomain) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "trust_domain is invalid: %v", err) + } + + var dnsPatterns []*regexp.Regexp + for _, r := range hclConfig.AllowedDNSPatterns { + re := regexp.MustCompile(r) + dnsPatterns = append(dnsPatterns, re) + } + + allowNonRootPorts := true + if hclConfig.AllowNonRootPorts != nil { + allowNonRootPorts = *hclConfig.AllowNonRootPorts + } + + tofu := true + if hclConfig.TOFU != nil { + tofu = *hclConfig.TOFU + } + + mustUseTOFU := false + switch { + // User has explicitly asked for a required port that is untrusted + case hclConfig.RequiredPort != nil && *hclConfig.RequiredPort >= 1024: + mustUseTOFU = true + // User has just chosen the defaults, any port is allowed + case hclConfig.AllowNonRootPorts == nil && hclConfig.RequiredPort == nil: + mustUseTOFU = true + // User explicitly set AllowNonRootPorts to true and no required port specified + case hclConfig.AllowNonRootPorts != nil && *hclConfig.AllowNonRootPorts && hclConfig.RequiredPort == nil: + mustUseTOFU = true + } + + if !tofu && mustUseTOFU { + return nil, status.Errorf(codes.InvalidArgument, "you can not turn off trust on first use (TOFU) when non-root ports are allowed") + } + + p.setConfiguration(&configuration{ + trustDomain: trustDomain, + dnsPatterns: dnsPatterns, + requiredPort: hclConfig.RequiredPort, + allowNonRootPorts: allowNonRootPorts, + tofu: tofu, + }) + + return &configv1.ConfigureResponse{}, nil +} + +// SetLogger sets this plugin's logger +func (p *Plugin) SetLogger(log hclog.Logger) { + p.log = log +} + +func (p *Plugin) getConfig() (*configuration, error) { + p.m.Lock() + defer p.m.Unlock() + if p.config == nil { + return nil, status.Errorf(codes.FailedPrecondition, "not configured") + } + return p.config, nil +} + +func (p *Plugin) setConfiguration(config *configuration) { + p.m.Lock() + defer p.m.Unlock() + p.config = config +} + +func buildSelectorValues(hostName string) []string { + var selectorValues []string + + selectorValues = append(selectorValues, "hostname:"+hostName) + + return selectorValues +} + +func validateAgentName(agentName string) error { + l := agentNamePattern.FindAllStringSubmatch(agentName, -1) + if len(l) != 1 || len(l[0]) == 0 || len(l[0]) > 32 { + return status.Error(codes.InvalidArgument, "agent name is not valid") + } + return nil +} + +func validateHostName(hostName string, dnsPatterns []*regexp.Regexp) error { + if hostName == "localhost" { + return status.Errorf(codes.PermissionDenied, "you can not use localhost as a hostname") + } + if len(dnsPatterns) == 0 { + return nil + } + for _, re := range dnsPatterns { + l := re.FindAllStringSubmatch(hostName, -1) + if len(l) > 0 { + return nil + } + } + return status.Errorf(codes.PermissionDenied, "the requested hostname is not allowed to connect") +} diff --git a/pkg/server/plugin/nodeattestor/httpchallenge/httpchallenge_test.go b/pkg/server/plugin/nodeattestor/httpchallenge/httpchallenge_test.go new file mode 100644 index 00000000000..cdde03f8e98 --- /dev/null +++ b/pkg/server/plugin/nodeattestor/httpchallenge/httpchallenge_test.go @@ -0,0 +1,433 @@ +package httpchallenge_test + +import ( + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "net/http/httptest" + neturl "net/url" + "testing" + + "github.com/spiffe/go-spiffe/v2/spiffeid" + agentstorev1 "github.com/spiffe/spire-plugin-sdk/proto/spire/hostservice/server/agentstore/v1" + configv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/service/common/config/v1" + "github.com/spiffe/spire/pkg/common/catalog" + common_httpchallenge "github.com/spiffe/spire/pkg/common/plugin/httpchallenge" + "github.com/spiffe/spire/pkg/server/plugin/nodeattestor" + "github.com/spiffe/spire/pkg/server/plugin/nodeattestor/httpchallenge" + "github.com/spiffe/spire/proto/spire/common" + "github.com/spiffe/spire/test/fakes/fakeagentstore" + "github.com/spiffe/spire/test/plugintest" + "github.com/stretchr/testify/require" +) + +func TestConfigure(t *testing.T) { + tests := []struct { + name string + hclConf string + coreConf *configv1.CoreConfiguration + expErr string + }{ + { + name: "Configure fails if core config is not provided", + expErr: "rpc error: code = InvalidArgument desc = core configuration is required", + }, + { + name: "Configure fails if trust domain is empty", + expErr: "rpc error: code = InvalidArgument desc = trust_domain is required", + coreConf: &configv1.CoreConfiguration{}, + }, + { + name: "Configure fails if HCL config cannot be decoded", + expErr: "rpc error: code = InvalidArgument desc = unable to decode configuration", + coreConf: &configv1.CoreConfiguration{TrustDomain: "example.org"}, + hclConf: "not an HCL configuration", + }, + { + name: "Configure fails if tofu and allow_non_root_ports", + expErr: "rpc error: code = InvalidArgument desc = you can not turn off trust on first use (TOFU) when non-root ports are allowed", + coreConf: &configv1.CoreConfiguration{TrustDomain: "example.org"}, + hclConf: "tofu = false\nallow_non_root_ports = true", + }, + { + name: "Configure fails if tofu and required port >= 1024", + expErr: "rpc error: code = InvalidArgument desc = you can not turn off trust on first use (TOFU) when non-root ports are allowed", + coreConf: &configv1.CoreConfiguration{TrustDomain: "example.org"}, + hclConf: "tofu = false\nrequired_port = 1024", + }, + { + name: "Configure fails if tofu and no other args", + expErr: "rpc error: code = InvalidArgument desc = you can not turn off trust on first use (TOFU) when non-root ports are allowed", + coreConf: &configv1.CoreConfiguration{TrustDomain: "example.org"}, + hclConf: "tofu = false", + }, + { + name: "Configure fails if tofu and allow root ports is true", + expErr: "rpc error: code = InvalidArgument desc = you can not turn off trust on first use (TOFU) when non-root ports are allowed", + coreConf: &configv1.CoreConfiguration{TrustDomain: "example.org"}, + hclConf: "tofu = false\nallow_non_root_ports = true", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + plugin := httpchallenge.New() + resp, err := plugin.Configure(context.Background(), &configv1.ConfigureRequest{ + HclConfiguration: tt.hclConf, + CoreConfiguration: tt.coreConf, + }) + if tt.expErr != "" { + require.Contains(t, err.Error(), tt.expErr) + require.Nil(t, resp) + return + } + require.NoError(t, err) + require.NotNil(t, resp) + }) + } +} + +func TestAttestFailures(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/.well-known/spiffe/nodeattestor/http_challenge/default/challenge" { + t.Errorf("Expected to request '/.well-known/spiffe/nodeattestor/http_challenge/default/challenge', got: %s", r.URL.Path) + } + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`123456789abcdefghijklmnopqrstuvwxyz`)) + })) + defer server.Close() + + client := newClientWithLocalIntercept(server.URL) + + challengeFnNil := func(ctx context.Context, challenge []byte) ([]byte, error) { + return nil, nil + } + + tests := []struct { + name string + hclConf string + expErr string + payload []byte + challengeFn func(ctx context.Context, challenge []byte) ([]byte, error) + tofu bool + }{ + { + name: "Attest fails if payload doesnt exist", + expErr: "rpc error: code = InvalidArgument desc = payload cannot be empty", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: nil, + }, + { + name: "Attest fails if payload cannot be unmarshalled", + expErr: "rpc error: code = InvalidArgument desc = nodeattestor(http_challenge): failed to unmarshal data: invalid character 'o' in literal null (expecting 'u')", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: []byte("not a payload"), + }, + { + name: "Attest fails if hostname is blank", + expErr: "rpc error: code = PermissionDenied desc = nodeattestor(http_challenge): challenge verification failed: hostname must be set", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "", + AgentName: "default", + Port: 80, + }), + }, + { + name: "Attest fails if agentname is blank", + expErr: "rpc error: code = InvalidArgument desc = nodeattestor(http_challenge): agent name is not valid", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo", + AgentName: "", + Port: 80, + }), + }, + { + name: "Attest fails if hostname is localhost", + expErr: "rpc error: code = PermissionDenied desc = nodeattestor(http_challenge): you can not use localhost as a hostname", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "localhost", + AgentName: "default", + Port: 80, + }), + }, + { + name: "Attest fails if port is 0", + expErr: "port is invalid", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo", + AgentName: "default", + Port: 0, + }), + }, + { + name: "Attest fails if port is negative", + expErr: "port is invalid", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo", + AgentName: "default", + Port: -1, + }), + }, + { + name: "Attest fails if hostname has a slash", + expErr: "rpc error: code = PermissionDenied desc = nodeattestor(http_challenge): challenge verification failed: hostname can not contain a slash", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "fo/o", + AgentName: "default", + Port: 80, + }), + }, + { + name: "Attest fails if hostname has a colon", + expErr: "rpc error: code = PermissionDenied desc = nodeattestor(http_challenge): challenge verification failed: hostname can not contain a colon", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo:1", + AgentName: "default", + Port: 80, + }), + }, + { + name: "Attest fails if agentname has a dot", + expErr: "rpc error: code = InvalidArgument desc = nodeattestor(http_challenge): agent name is not valid", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo", + AgentName: "def.ault", + Port: 80, + }), + }, + { + name: "Attest fails if required port is different from given one", + expErr: "rpc error: code = InvalidArgument desc = nodeattestor(http_challenge): port 81 is not allowed to be used by this server", + hclConf: "required_port = 80", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo", + AgentName: "default", + Port: 81, + }), + }, + { + name: "Attest fails if non root ports are disallowed and port is >= 1024", + expErr: "rpc error: code = InvalidArgument desc = nodeattestor(http_challenge): port 1024 is not allowed to be >= 1024", + hclConf: "allow_non_root_ports = false", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo", + AgentName: "default", + Port: 1024, + }), + }, + { + name: "Attest fails if hostname is not valid by dns pattern", + expErr: "rpc error: code = PermissionDenied desc = nodeattestor(http_challenge): the requested hostname is not allowed to connect", + hclConf: `allowed_dns_patterns = ["p[0-9][.]example[.]com"]`, + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo", + AgentName: "default", + Port: 80, + }), + }, + { + name: "Attest fails if nonce does not match", + expErr: "rpc error: code = PermissionDenied desc = nodeattestor(http_challenge): challenge verification failed: expected nonce \"bad123456789abcdefghijklmnopqrstuvwxyz\" but got \"123456789abcdefghijklmnopqrstuvwxyz\"", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo", + AgentName: "default", + Port: 80, + }), + }, + { + name: "Attest fails when reattesting with tofu", + expErr: "rpc error: code = PermissionDenied desc = nodeattestor(http_challenge): attestation data has already been used to attest an agent", + hclConf: "", + tofu: false, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo", + AgentName: "default", + Port: 80, + }), + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + var testNonce string + if tt.tofu { + testNonce = "bad123456789abcdefghijklmnopqrstuvwxyz" + } else { + testNonce = "123456789abcdefghijklmnopqrstuvwxyz" + } + plugin := loadPlugin(t, tt.hclConf, !tt.tofu, client, testNonce) + result, err := plugin.Attest(context.Background(), tt.payload, tt.challengeFn) + require.Contains(t, err.Error(), tt.expErr) + require.Nil(t, result) + }) + } +} + +func TestAttestSucceeds(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/.well-known/spiffe/nodeattestor/http_challenge/default/challenge" { + t.Errorf("Expected to request '/.well-known/spiffe/nodeattestor/http_challenge/default/challenge', got: %s", r.URL.Path) + } + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`123456789abcdefghijklmnopqrstuvwxyz`)) + })) + defer server.Close() + + client := newClientWithLocalIntercept(server.URL) + + challengeFnNil := func(ctx context.Context, challenge []byte) ([]byte, error) { + return nil, nil + } + + tests := []struct { + name string + hclConf string + payload []byte + challengeFn func(ctx context.Context, challenge []byte) ([]byte, error) + expectedAgentID string + expectedSelectors []*common.Selector + tofu bool + }{ + { + name: "Attest succeeds for defaults", + hclConf: "", + tofu: true, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo", + AgentName: "default", + Port: 80, + }), + expectedAgentID: "spiffe://example.org/spire/agent/http_challenge/foo", + expectedSelectors: []*common.Selector{ + { + Type: "http_challenge", + Value: "hostname:foo", + }, + }, + }, + { + name: "Attest succeeds for reattest without tofu", + hclConf: "tofu = false\nallow_non_root_ports = false", + tofu: false, + challengeFn: challengeFnNil, + payload: marshalPayload(t, &common_httpchallenge.AttestationData{ + HostName: "foo", + AgentName: "default", + Port: 80, + }), + expectedAgentID: "spiffe://example.org/spire/agent/http_challenge/foo", + expectedSelectors: []*common.Selector{ + { + Type: "http_challenge", + Value: "hostname:foo", + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + testNonce := "123456789abcdefghijklmnopqrstuvwxyz" + plugin := loadPlugin(t, tt.hclConf, !tt.tofu, client, testNonce) + result, err := plugin.Attest(context.Background(), tt.payload, tt.challengeFn) + require.NoError(t, err) + require.NotNil(t, result) + + require.Equal(t, tt.expectedAgentID, result.AgentID) + requireSelectorsMatch(t, tt.expectedSelectors, result.Selectors) + }) + } +} + +func loadPlugin(t *testing.T, config string, testTOFU bool, client *http.Client, testNonce string) nodeattestor.NodeAttestor { + v1 := new(nodeattestor.V1) + agentStore := fakeagentstore.New() + var configureErr error + if testTOFU { + agentStore.SetAgentInfo(&agentstorev1.AgentInfo{ + AgentId: "spiffe://example.org/spire/agent/http_challenge/foo", + }) + } + opts := []plugintest.Option{ + plugintest.Configure(config), + plugintest.CaptureConfigureError(&configureErr), + plugintest.HostServices(agentstorev1.AgentStoreServiceServer(agentStore)), + plugintest.CoreConfig(catalog.CoreConfig{ + TrustDomain: spiffeid.RequireTrustDomainFromString("example.org"), + }), + } + plugintest.Load(t, httpchallenge.BuiltInTesting(client, testNonce), v1, opts...) + return v1 +} + +func marshalPayload(t *testing.T, attReq *common_httpchallenge.AttestationData) []byte { + attReqBytes, err := json.Marshal(attReq) + require.NoError(t, err) + return attReqBytes +} + +func requireSelectorsMatch(t *testing.T, expected []*common.Selector, actual []*common.Selector) { + require.Equal(t, len(expected), len(actual)) + for idx, expSel := range expected { + require.Equal(t, expSel.Type, actual[idx].Type) + require.Equal(t, expSel.Value, actual[idx].Value) + } +} + +func newClientWithLocalIntercept(url string) *http.Client { + u, _ := neturl.Parse(url) + _, port, _ := net.SplitHostPort(u.Host) + return &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + defaultDialContext := http.DefaultTransport.(*http.Transport).DialContext + if addr == "foo:80" { + addr = fmt.Sprintf("127.0.0.1:%s", port) + } + return defaultDialContext(ctx, network, addr) + }, + }, + } +}