From be8667ebdfda538d459727f9757eafd7421f2496 Mon Sep 17 00:00:00 2001 From: ajay1135 <32616412+ajay1135@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:07:24 -0400 Subject: [PATCH] Introduce `aws_rolesanywhere_trustanchor` BundlePublisher plugin (#5048) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduce the aws_rolesanywhere_trustanchor BundlePublisher plugin * Implement and add tests for the plugin Signed-off-by: Ajay Gupta * Add documentation for aws_rolesanywhere_trustanchor BundlePublisher plugin Signed-off-by: Ajay Gupta * Apply suggestions from code review Co-authored-by: Agustín Martínez Fayó Signed-off-by: ajay1135 <32616412+ajay1135@users.noreply.github.com> * Address comments on PR * Only required rolesanywhere:UpdateTrustAnchor permissions (no creating or listing) * Add note about how this plugin is only supported when an UpstreamAuthority plugin is also used * Use ID instead of trust anchor name to identify trust anchors, as it's unique * Check that certificate bundles don't exceed a length of 8000 before making the UpdateTrustAnchor API call * Make corresponding changes to unit tests Signed-off-by: Ajay Gupta * Apply suggestions from code review Co-authored-by: Agustín Martínez Fayó Signed-off-by: ajay1135 <32616412+ajay1135@users.noreply.github.com> --------- Signed-off-by: Ajay Gupta Signed-off-by: ajay1135 <32616412+ajay1135@users.noreply.github.com> Co-authored-by: Agustín Martínez Fayó --- conf/server/server_full.conf | 20 + ...publisher_aws_rolesanywhere_trustanchor.md | 33 ++ doc/spire_server.md | 55 +-- go.mod | 7 +- go.sum | 14 +- pkg/server/catalog/bundlepublisher.go | 2 + .../awsrolesanywhere/awsrolesanywhere.go | 211 ++++++++++ .../awsrolesanywhere/awsrolesanywhere_test.go | 376 ++++++++++++++++++ .../awsrolesanywhere/client.go | 33 ++ test/fixture/certs/large_bundle.der | Bin 0 -> 7803 bytes test/util/cert_fixtures.go | 15 +- 11 files changed, 725 insertions(+), 41 deletions(-) create mode 100644 doc/plugin_server_bundlepublisher_aws_rolesanywhere_trustanchor.md create mode 100644 pkg/server/plugin/bundlepublisher/awsrolesanywhere/awsrolesanywhere.go create mode 100644 pkg/server/plugin/bundlepublisher/awsrolesanywhere/awsrolesanywhere_test.go create mode 100644 pkg/server/plugin/bundlepublisher/awsrolesanywhere/client.go create mode 100644 test/fixture/certs/large_bundle.der diff --git a/conf/server/server_full.conf b/conf/server/server_full.conf index 31d64bfd67..c346ec8dba 100644 --- a/conf/server/server_full.conf +++ b/conf/server/server_full.conf @@ -929,6 +929,26 @@ plugins { # # format = "spiffe" # } # } + + # BundlePublisher "aws_rolesanywhere_trustanchor": A bundle publisher that puts the current trust + # bundle of the server in an AWS IAM Roles Anywhere trust anchor, keeping it updated. + # BundlePublisher "aws_rolesanywhere_trustanchor" { + # plugin_data { + # # region: AWS region to store the trust bundle. Default: "". + # # region = "us-east-1" + + # # access_key_id: AWS access key id. Default: value of + # # AWS_ACCESS_KEY_ID environment variable. + # # access_key_id = "" + + # # secret_access_key: AWS secret access key. Default: value of + # # AWS_SECRET_ACCESS_KEY environment variable. + # # secret_access_key = "" + + # # trust_anchor_id: The AWS IAM Roles Anywhere trust anchor id of the trust anchor to which to put the trust bundle. Default: "". + # # trust_anchor_id = "153d3e58-cab5-4a59-a0a1-3febad2937c4" + # } + # } } # telemetry: If telemetry is desired use this section to configure the diff --git a/doc/plugin_server_bundlepublisher_aws_rolesanywhere_trustanchor.md b/doc/plugin_server_bundlepublisher_aws_rolesanywhere_trustanchor.md new file mode 100644 index 0000000000..4b751f7b34 --- /dev/null +++ b/doc/plugin_server_bundlepublisher_aws_rolesanywhere_trustanchor.md @@ -0,0 +1,33 @@ +# Server plugin: BundlePublisher "aws_rolesanywhere_trustanchor" + +> [!WARNING] +> This plugin is only supported when an UpstreamAuthority plugin is used. + +The `aws_rolesanywhere_trustanchor` plugin puts the current trust bundle of the server +in a trust anchor, keeping it updated. + +The plugin accepts the following configuration options: + +| Configuration | Description | Required | Default | +|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|------------------------------------------------------| +| access_key_id | AWS access key id. | Required only if AWS credentials aren't otherwise set in the environment. | Value of AWS_ACCESS_KEY_ID environment variable. | +| secret_access_key | AWS secret access key. | Required only if AWS credentials aren't otherwise set in the environment. | Value of AWS_SECRET_ACCESS_KEY environment variable. | +| region | AWS region to store the trust bundle. | Yes. | | +| trust_anchor_id | The AWS IAM Roles Anywhere trust anchor id of the trust anchor to which to put the trust bundle. | Yes. | | + +## AWS IAM Permissions + +The user identified by the configured credentials needs to have `rolesanywhere:UpdateTrustAnchor` permissions. + +## Sample configuration + +The following configuration puts the local trust bundle contents into the `spire-trust-anchor` trust anchor and keeps it updated. The AWS credentials are obtained from the environment. + +```hcl + BundlePublisher "aws_rolesanywhere_trustanchor" { + plugin_data { + region = "us-east-1" + trust_anchor_id = "153d3e58-cab5-4a59-a0a1-3febad2937c4" + } + } +``` diff --git a/doc/spire_server.md b/doc/spire_server.md index 54e95e3315..1304225cc7 100644 --- a/doc/spire_server.md +++ b/doc/spire_server.md @@ -16,33 +16,34 @@ This document is a configuration reference for SPIRE Server. It includes informa ## Built-in plugins -| Type | Name | Description | -|--------------------|----------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------| -| DataStore | [sql](/doc/plugin_server_datastore_sql.md) | An SQL database storage for SQLite, PostgreSQL and MySQL databases for the SPIRE datastore | -| KeyManager | [aws_kms](/doc/plugin_server_keymanager_aws_kms.md) | A key manager which manages keys in AWS KMS | -| KeyManager | [disk](/doc/plugin_server_keymanager_disk.md) | A key manager which manages keys persisted on disk | -| KeyManager | [memory](/doc/plugin_server_keymanager_memory.md) | A key manager which manages unpersisted keys in memory | -| CredentialComposer | [uniqueid](/doc/plugin_server_credentialcomposer_uniqueid.md) | Adds the x509UniqueIdentifier attribute to workload X509-SVIDs. | -| NodeAttestor | [aws_iid](/doc/plugin_server_nodeattestor_aws_iid.md) | A node attestor which attests agent identity using an AWS Instance Identity Document | -| NodeAttestor | [azure_msi](/doc/plugin_server_nodeattestor_azure_msi.md) | A node attestor which attests agent identity using an Azure MSI token | -| NodeAttestor | [gcp_iit](/doc/plugin_server_nodeattestor_gcp_iit.md) | A node attestor which attests agent identity using a GCP Instance Identity Token | -| NodeAttestor | [join_token](/doc/plugin_server_nodeattestor_jointoken.md) | A node attestor which validates agents attesting with server-generated join tokens | -| NodeAttestor | [k8s_sat](/doc/plugin_server_nodeattestor_k8s_sat.md) (deprecated) | A node attestor which attests agent identity using a Kubernetes Service Account token | -| NodeAttestor | [k8s_psat](/doc/plugin_server_nodeattestor_k8s_psat.md) | A node attestor which attests agent identity using a Kubernetes Projected Service Account token | -| NodeAttestor | [sshpop](/doc/plugin_server_nodeattestor_sshpop.md) | A node attestor which attests agent identity using an existing ssh certificate | -| NodeAttestor | [tpm_devid](/doc/plugin_server_nodeattestor_tpm_devid.md) | A node attestor which attests agent identity using a TPM that has been provisioned with a DevID certificate | -| NodeAttestor | [x509pop](/doc/plugin_server_nodeattestor_x509pop.md) | A node attestor which attests agent identity using an existing X.509 certificate | -| UpstreamAuthority | [disk](/doc/plugin_server_upstreamauthority_disk.md) | Uses a CA loaded from disk to sign SPIRE server intermediate certificates. | -| UpstreamAuthority | [aws_pca](/doc/plugin_server_upstreamauthority_aws_pca.md) | Uses a Private Certificate Authority from AWS Certificate Manager to sign SPIRE server intermediate certificates. | -| UpstreamAuthority | [awssecret](/doc/plugin_server_upstreamauthority_awssecret.md) | Uses a CA loaded from AWS SecretsManager to sign SPIRE server intermediate certificates. | -| UpstreamAuthority | [gcp_cas](/doc/plugin_server_upstreamauthority_gcp_cas.md) | Uses a Private Certificate Authority from GCP Certificate Authority Service to sign SPIRE Server intermediate certificates. | -| UpstreamAuthority | [vault](/doc/plugin_server_upstreamauthority_vault.md) | Uses a PKI Secret Engine from HashiCorp Vault to sign SPIRE server intermediate certificates. | -| UpstreamAuthority | [spire](/doc/plugin_server_upstreamauthority_spire.md) | Uses an upstream SPIRE server in the same trust domain to obtain intermediate signing certificates for SPIRE server. | -| UpstreamAuthority | [cert-manager](/doc/plugin_server_upstreamauthority_cert_manager.md) | Uses a referenced cert-manager Issuer to request intermediate signing certificates. | -| Notifier | [gcs_bundle](/doc/plugin_server_notifier_gcs_bundle.md) | A notifier that pushes the latest trust bundle contents into an object in Google Cloud Storage. | -| Notifier | [k8sbundle](/doc/plugin_server_notifier_k8sbundle.md) | A notifier that pushes the latest trust bundle contents into a Kubernetes ConfigMap. | -| BundlePublisher | [aws_s3](/doc/plugin_server_bundlepublisher_aws_s3.md) | Publishes the trust bundle to an Amazon S3 bucket. | -| BundlePublisher | [gcp_cloudstorage](/doc/plugin_server_bundlepublisher_gcp_cloudstorage.md) | Publishes the trust bundle to a Google Cloud Storage bucket. | +| Type | Name | Description | +|--------------------|--------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------| +| DataStore | [sql](/doc/plugin_server_datastore_sql.md) | An SQL database storage for SQLite, PostgreSQL and MySQL databases for the SPIRE datastore | +| KeyManager | [aws_kms](/doc/plugin_server_keymanager_aws_kms.md) | A key manager which manages keys in AWS KMS | +| KeyManager | [disk](/doc/plugin_server_keymanager_disk.md) | A key manager which manages keys persisted on disk | +| KeyManager | [memory](/doc/plugin_server_keymanager_memory.md) | A key manager which manages unpersisted keys in memory | +| CredentialComposer | [uniqueid](/doc/plugin_server_credentialcomposer_uniqueid.md) | Adds the x509UniqueIdentifier attribute to workload X509-SVIDs. | +| NodeAttestor | [aws_iid](/doc/plugin_server_nodeattestor_aws_iid.md) | A node attestor which attests agent identity using an AWS Instance Identity Document | +| NodeAttestor | [azure_msi](/doc/plugin_server_nodeattestor_azure_msi.md) | A node attestor which attests agent identity using an Azure MSI token | +| NodeAttestor | [gcp_iit](/doc/plugin_server_nodeattestor_gcp_iit.md) | A node attestor which attests agent identity using a GCP Instance Identity Token | +| NodeAttestor | [join_token](/doc/plugin_server_nodeattestor_jointoken.md) | A node attestor which validates agents attesting with server-generated join tokens | +| NodeAttestor | [k8s_sat](/doc/plugin_server_nodeattestor_k8s_sat.md) (deprecated) | A node attestor which attests agent identity using a Kubernetes Service Account token | +| NodeAttestor | [k8s_psat](/doc/plugin_server_nodeattestor_k8s_psat.md) | A node attestor which attests agent identity using a Kubernetes Projected Service Account token | +| NodeAttestor | [sshpop](/doc/plugin_server_nodeattestor_sshpop.md) | A node attestor which attests agent identity using an existing ssh certificate | +| NodeAttestor | [tpm_devid](/doc/plugin_server_nodeattestor_tpm_devid.md) | A node attestor which attests agent identity using a TPM that has been provisioned with a DevID certificate | +| NodeAttestor | [x509pop](/doc/plugin_server_nodeattestor_x509pop.md) | A node attestor which attests agent identity using an existing X.509 certificate | +| UpstreamAuthority | [disk](/doc/plugin_server_upstreamauthority_disk.md) | Uses a CA loaded from disk to sign SPIRE server intermediate certificates. | +| UpstreamAuthority | [aws_pca](/doc/plugin_server_upstreamauthority_aws_pca.md) | Uses a Private Certificate Authority from AWS Certificate Manager to sign SPIRE server intermediate certificates. | +| UpstreamAuthority | [awssecret](/doc/plugin_server_upstreamauthority_awssecret.md) | Uses a CA loaded from AWS SecretsManager to sign SPIRE server intermediate certificates. | +| UpstreamAuthority | [gcp_cas](/doc/plugin_server_upstreamauthority_gcp_cas.md) | Uses a Private Certificate Authority from GCP Certificate Authority Service to sign SPIRE Server intermediate certificates. | +| UpstreamAuthority | [vault](/doc/plugin_server_upstreamauthority_vault.md) | Uses a PKI Secret Engine from HashiCorp Vault to sign SPIRE server intermediate certificates. | +| UpstreamAuthority | [spire](/doc/plugin_server_upstreamauthority_spire.md) | Uses an upstream SPIRE server in the same trust domain to obtain intermediate signing certificates for SPIRE server. | +| UpstreamAuthority | [cert-manager](/doc/plugin_server_upstreamauthority_cert_manager.md) | Uses a referenced cert-manager Issuer to request intermediate signing certificates. | +| Notifier | [gcs_bundle](/doc/plugin_server_notifier_gcs_bundle.md) | A notifier that pushes the latest trust bundle contents into an object in Google Cloud Storage. | +| Notifier | [k8sbundle](/doc/plugin_server_notifier_k8sbundle.md) | A notifier that pushes the latest trust bundle contents into a Kubernetes ConfigMap. | +| BundlePublisher | [aws_s3](/doc/plugin_server_bundlepublisher_aws_s3.md) | Publishes the trust bundle to an Amazon S3 bucket. | +| BundlePublisher | [gcp_cloudstorage](/doc/plugin_server_bundlepublisher_gcp_cloudstorage.md) | Publishes the trust bundle to a Google Cloud Storage bucket. | +| BundlePublisher | [aws_rolesanywhere_trustanchor](/doc/plugin_server_bundlepublisher_rolesanywhere_trustanchor.md) | Publishes the trust bundle to an AWS IAM Roles Anywhere trust anchor. | ## Server configuration file diff --git a/go.mod b/go.mod index d4cad55abd..c2782a25a7 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/GoogleCloudPlatform/cloudsql-proxy v1.35.4 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.0 + github.com/aws/aws-sdk-go-v2 v1.30.1 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 @@ -27,6 +27,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/iam v1.33.0 github.com/aws/aws-sdk-go-v2/service/kms v1.34.0 github.com/aws/aws-sdk-go-v2/service/organizations v1.28.0 + github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.13.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.56.0 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.31.0 github.com/aws/aws-sdk-go-v2/service/sts v1.29.0 @@ -136,8 +137,8 @@ require ( github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.11 // indirect github.com/aws/aws-sdk-go-v2/service/ecr v1.24.7 // indirect diff --git a/go.sum b/go.sum index ff9cf0bb2f..22ad57a281 100644 --- a/go.sum +++ b/go.sum @@ -565,8 +565,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d 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-v2 v1.30.0 h1:6qAwtzlfcTtcL8NHtbDQAqgM5s6NDipQTkPxyH/6kAA= -github.com/aws/aws-sdk-go-v2 v1.30.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o= +github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk= @@ -577,10 +577,10 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzS 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/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.12 h1:SJ04WXGTwnHlWIODtC5kJzKbeuHt+OUNOgKg7nfnUGw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12/go.mod h1:FkpvXhA92gb3GE9LD6Og0pHHycTxW7xGpnEh5E7Opwo= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12 h1:hb5KgeYfObi5MHkSSZMEudnIvX30iB+E21evI4r6BnQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12/go.mod h1:CroKe/eWJdyfy9Vx4rljP5wTUjNJfb+fPz1uMYUhEGM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.11 h1:jJ2dythFP5oNunvwc3gBsINl3ZPt/InVm4a5OAr3tag= @@ -607,6 +607,8 @@ github.com/aws/aws-sdk-go-v2/service/kms v1.34.0 h1:GKrvkdgKYFjn5XMwzuuN+e9St/Tn github.com/aws/aws-sdk-go-v2/service/kms v1.34.0/go.mod h1:AStnoP2Hj6IHB5nHbVw2mEiGjLpOJPxY+XOc2xcKTvU= github.com/aws/aws-sdk-go-v2/service/organizations v1.28.0 h1:tm2U9o4/Pj6Sj1WYshqyTr3eF0sZviLn/UIElhyeBTA= github.com/aws/aws-sdk-go-v2/service/organizations v1.28.0/go.mod h1:gsyCAmtG8IgmzfqBQiduGEOhBrIVAZsGv7S4rCvXFRM= +github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.13.1 h1:2p65lTZ1OGnAGdDsMGFolNT8v0RAr2pF5eAo0jhgSlA= +github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.13.1/go.mod h1:43wn4yPVFL3PHXixCOGzLb8LwWJovqlFQz3qGOAkcYY= github.com/aws/aws-sdk-go-v2/service/s3 v1.56.0 h1:NZIFz15bhrWwewGU0tdUGsisKPQxvzy3O4dL5jgBDKw= github.com/aws/aws-sdk-go-v2/service/s3 v1.56.0/go.mod h1:ha/DkVoeDtS0XwRKyOiXP2J4Vzo3zpiE0yGi7Ej0X3o= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.31.0 h1:ZyB15ar3Z+zYlFbg0p9cRwu8MjanG70q+wR8/QI/Ehw= diff --git a/pkg/server/catalog/bundlepublisher.go b/pkg/server/catalog/bundlepublisher.go index fd425749f7..e051bb219e 100644 --- a/pkg/server/catalog/bundlepublisher.go +++ b/pkg/server/catalog/bundlepublisher.go @@ -3,6 +3,7 @@ package catalog import ( "github.com/spiffe/spire/pkg/common/catalog" "github.com/spiffe/spire/pkg/server/plugin/bundlepublisher" + "github.com/spiffe/spire/pkg/server/plugin/bundlepublisher/awsrolesanywhere" "github.com/spiffe/spire/pkg/server/plugin/bundlepublisher/awss3" "github.com/spiffe/spire/pkg/server/plugin/bundlepublisher/gcpcloudstorage" ) @@ -27,6 +28,7 @@ func (repo *bundlePublisherRepository) BuiltIns() []catalog.BuiltIn { return []catalog.BuiltIn{ awss3.BuiltIn(), gcpcloudstorage.BuiltIn(), + awsrolesanywhere.BuiltIn(), } } diff --git a/pkg/server/plugin/bundlepublisher/awsrolesanywhere/awsrolesanywhere.go b/pkg/server/plugin/bundlepublisher/awsrolesanywhere/awsrolesanywhere.go new file mode 100644 index 0000000000..803566c1bc --- /dev/null +++ b/pkg/server/plugin/bundlepublisher/awsrolesanywhere/awsrolesanywhere.go @@ -0,0 +1,211 @@ +package awsrolesanywhere + +import ( + "context" + "sync" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rolesanywhere" + rolesanywheretypes "github.com/aws/aws-sdk-go-v2/service/rolesanywhere/types" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/hcl" + "github.com/spiffe/spire-plugin-sdk/pluginsdk/support/bundleformat" + bundlepublisherv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/server/bundlepublisher/v1" + "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/types" + configv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/service/common/config/v1" + "github.com/spiffe/spire/pkg/common/catalog" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +const ( + pluginName = "aws_rolesanywhere_trustanchor" +) + +type pluginHooks struct { + newRolesAnywhereClientFunc func(c aws.Config) (rolesAnywhere, error) +} + +func BuiltIn() catalog.BuiltIn { + return builtin(New()) +} + +func New() *Plugin { + return newPlugin(newRolesAnywhereClient) +} + +// Config holds the configuration of the plugin. +type Config struct { + AccessKeyID string `hcl:"access_key_id" json:"access_key_id"` + SecretAccessKey string `hcl:"secret_access_key" json:"secret_access_key"` + Region string `hcl:"region" json:"region"` + TrustAnchorID string `hcl:"trust_anchor_id" json:"trust_anchor_id"` +} + +// Plugin is the main representation of this bundle publisher plugin. +type Plugin struct { + bundlepublisherv1.UnsafeBundlePublisherServer + configv1.UnsafeConfigServer + + config *Config + configMtx sync.RWMutex + + bundle *types.Bundle + bundleMtx sync.RWMutex + + hooks pluginHooks + rolesAnywhereClient rolesAnywhere + log hclog.Logger +} + +// SetLogger sets a logger in the plugin. +func (p *Plugin) SetLogger(log hclog.Logger) { + p.log = log +} + +// Configure configures the plugin. +func (p *Plugin) Configure(ctx context.Context, req *configv1.ConfigureRequest) (*configv1.ConfigureResponse, error) { + config, err := parseAndValidateConfig(req.HclConfiguration) + if err != nil { + return nil, err + } + + awsCfg, err := newAWSConfig(ctx, config) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to create client configuration: %v", err) + } + rolesAnywhere, err := p.hooks.newRolesAnywhereClientFunc(awsCfg) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to create client: %v", err) + } + p.rolesAnywhereClient = rolesAnywhere + + p.setConfig(config) + p.setBundle(nil) + return &configv1.ConfigureResponse{}, nil +} + +// PublishBundle puts the bundle in the Roles Anywhere trust anchor, with +// the configured id. +func (p *Plugin) PublishBundle(ctx context.Context, req *bundlepublisherv1.PublishBundleRequest) (*bundlepublisherv1.PublishBundleResponse, error) { + config, err := p.getConfig() + if err != nil { + return nil, err + } + + if req.Bundle == nil { + return nil, status.Error(codes.InvalidArgument, "missing bundle in request") + } + + currentBundle := p.getBundle() + if proto.Equal(req.GetBundle(), currentBundle) { + // Bundle not changed. No need to publish. + return &bundlepublisherv1.PublishBundleResponse{}, nil + } + + formatter := bundleformat.NewFormatter(req.GetBundle()) + bundleBytes, err := formatter.Format(bundleformat.PEM) + if err != nil { + return nil, status.Error(codes.Internal, "could not format bundle to PEM format") + } + bundleStr := string(bundleBytes) + + // To prevent flooding of the logs in the case that the bundle is + // too large. + if len(bundleStr) > 8000 { + return nil, status.Error(codes.InvalidArgument, "bundle too large") + } + + // Update the trust anchor that was found + updateTrustAnchorInput := rolesanywhere.UpdateTrustAnchorInput{ + TrustAnchorId: &config.TrustAnchorID, + Source: &rolesanywheretypes.Source{ + SourceType: rolesanywheretypes.TrustAnchorTypeCertificateBundle, + SourceData: &rolesanywheretypes.SourceDataMemberX509CertificateData{ + Value: bundleStr, + }, + }, + } + updateTrustAnchorOutput, err := p.rolesAnywhereClient.UpdateTrustAnchor(ctx, &updateTrustAnchorInput) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to update trust anchor: %v", err) + } + trustAnchorArn := *updateTrustAnchorOutput.TrustAnchor.TrustAnchorArn + trustAnchorName := *updateTrustAnchorOutput.TrustAnchor.Name + + p.setBundle(req.GetBundle()) + p.log.Debug("Bundle published", "arn", trustAnchorArn, "trust_anchor_name", trustAnchorName) + return &bundlepublisherv1.PublishBundleResponse{}, nil +} + +// getBundle gets the latest bundle that the plugin has. +func (p *Plugin) getBundle() *types.Bundle { + p.configMtx.RLock() + defer p.configMtx.RUnlock() + + return p.bundle +} + +// getConfig gets the configuration of the plugin. +func (p *Plugin) getConfig() (*Config, error) { + p.configMtx.RLock() + defer p.configMtx.RUnlock() + + if p.config == nil { + return nil, status.Error(codes.FailedPrecondition, "not configured") + } + return p.config, nil +} + +// setBundle updates the current bundle in the plugin with the provided bundle. +func (p *Plugin) setBundle(bundle *types.Bundle) { + p.bundleMtx.Lock() + defer p.bundleMtx.Unlock() + + p.bundle = bundle +} + +// setConfig sets the configuration for the plugin. +func (p *Plugin) setConfig(config *Config) { + p.configMtx.Lock() + defer p.configMtx.Unlock() + + p.config = config +} + +// builtin creates a new BundlePublisher built-in plugin. +func builtin(p *Plugin) catalog.BuiltIn { + return catalog.MakeBuiltIn(pluginName, + bundlepublisherv1.BundlePublisherPluginServer(p), + configv1.ConfigServiceServer(p), + ) +} + +// newPlugin returns a new plugin instance. +func newPlugin(newRolesAnywhereClientFunc func(c aws.Config) (rolesAnywhere, error)) *Plugin { + return &Plugin{ + hooks: pluginHooks{ + newRolesAnywhereClientFunc: newRolesAnywhereClientFunc, + }, + } +} + +// parseAndValidateConfig returns an error if any configuration provided does +// not meet acceptable criteria +func parseAndValidateConfig(c string) (*Config, error) { + config := new(Config) + + if err := hcl.Decode(config, c); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "unable to decode configuration: %v", err) + } + + if config.Region == "" { + return nil, status.Error(codes.InvalidArgument, "configuration is missing the region") + } + + if config.TrustAnchorID == "" { + return nil, status.Error(codes.InvalidArgument, "configuration is missing the trust anchor id") + } + return config, nil +} diff --git a/pkg/server/plugin/bundlepublisher/awsrolesanywhere/awsrolesanywhere_test.go b/pkg/server/plugin/bundlepublisher/awsrolesanywhere/awsrolesanywhere_test.go new file mode 100644 index 0000000000..c33d5ffb11 --- /dev/null +++ b/pkg/server/plugin/bundlepublisher/awsrolesanywhere/awsrolesanywhere_test.go @@ -0,0 +1,376 @@ +package awsrolesanywhere + +import ( + "context" + "crypto/x509" + "errors" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rolesanywhere" + rolesanywheretypes "github.com/aws/aws-sdk-go-v2/service/rolesanywhere/types" + "github.com/spiffe/go-spiffe/v2/spiffeid" + bundlepublisherv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/server/bundlepublisher/v1" + "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/types" + configv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/service/common/config/v1" + "github.com/spiffe/spire/pkg/common/catalog" + "github.com/spiffe/spire/test/plugintest" + "github.com/spiffe/spire/test/spiretest" + "github.com/spiffe/spire/test/util" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" +) + +func TestConfigure(t *testing.T) { + for _, tt := range []struct { + name string + + configureRequest *configv1.ConfigureRequest + newClientErr error + expectCode codes.Code + expectMsg string + config *Config + expectAWSConfig *aws.Config + }{ + { + name: "success", + config: &Config{ + AccessKeyID: "access-key-id", + SecretAccessKey: "secret-access-key", + Region: "region", + TrustAnchorID: "trust-anchor-id", + }, + }, + { + name: "no region", + config: &Config{ + TrustAnchorID: "trust-anchor-id", + }, + expectCode: codes.InvalidArgument, + expectMsg: "configuration is missing the region", + }, + { + name: "no trust anchor id", + config: &Config{ + Region: "region", + }, + expectCode: codes.InvalidArgument, + expectMsg: "configuration is missing the trust anchor id", + }, + { + name: "client error", + config: &Config{ + AccessKeyID: "access-key-id", + SecretAccessKey: "secret-access-key", + Region: "region", + TrustAnchorID: "trust-anchor-id", + }, + expectCode: codes.Internal, + expectMsg: "failed to create client: client creation error", + newClientErr: errors.New("client creation error"), + }, + } { + t.Run(tt.name, func(t *testing.T) { + var err error + options := []plugintest.Option{ + plugintest.CaptureConfigureError(&err), + plugintest.CoreConfig(catalog.CoreConfig{ + TrustDomain: spiffeid.RequireTrustDomainFromString("example.org"), + }), + plugintest.ConfigureJSON(tt.config), + } + + newClient := func(awsConfig aws.Config) (rolesAnywhere, error) { + if tt.newClientErr != nil { + return nil, tt.newClientErr + } + return &fakeClient{ + awsConfig: awsConfig, + }, nil + } + p := newPlugin(newClient) + + plugintest.Load(t, builtin(p), nil, options...) + spiretest.RequireGRPCStatusHasPrefix(t, err, tt.expectCode, tt.expectMsg) + + if tt.expectMsg != "" { + require.Nil(t, p.config) + return + } + + // Check that the plugin has the expected configuration. + require.Equal(t, tt.config, p.config) + + client, ok := p.rolesAnywhereClient.(*fakeClient) + require.True(t, ok) + + // It's important to check that the configuration has been wired + // up to the aws config, that needs to have the specified region + // and credentials. + require.Equal(t, tt.config.Region, client.awsConfig.Region) + creds, err := client.awsConfig.Credentials.Retrieve(context.Background()) + require.NoError(t, err) + require.Equal(t, tt.config.AccessKeyID, creds.AccessKeyID) + require.Equal(t, tt.config.SecretAccessKey, creds.SecretAccessKey) + }) + } +} + +func TestPublishBundle(t *testing.T) { + testBundle := getTestBundle(t) + + for _, tt := range []struct { + name string + + newClientErr error + expectCode codes.Code + expectMsg string + config *Config + bundle *types.Bundle + updateTrustAnchorErr error + }{ + { + name: "success", + bundle: testBundle, + config: &Config{ + AccessKeyID: "access-key-id", + SecretAccessKey: "secret-access-key", + Region: "region", + TrustAnchorID: "trust-anchor-id", + }, + }, + { + name: "multiple times", + bundle: testBundle, + config: &Config{ + AccessKeyID: "access-key-id", + SecretAccessKey: "secret-access-key", + Region: "region", + TrustAnchorID: "trust-anchor-id", + }, + }, + { + name: "update trust anchor failure", + bundle: testBundle, + config: &Config{ + AccessKeyID: "access-key-id", + SecretAccessKey: "secret-access-key", + Region: "region", + TrustAnchorID: "trust-anchor-id", + }, + updateTrustAnchorErr: errors.New("some error"), + expectCode: codes.Internal, + expectMsg: "failed to update trust anchor: some error", + }, + { + name: "not configured", + expectCode: codes.FailedPrecondition, + expectMsg: "not configured", + }, + { + name: "missing bundle", + config: &Config{ + AccessKeyID: "access-key-id", + SecretAccessKey: "secret-access-key", + Region: "region", + TrustAnchorID: "trust-anchor-id", + }, + expectCode: codes.InvalidArgument, + expectMsg: "missing bundle in request", + }, + } { + t.Run(tt.name, func(t *testing.T) { + var err error + options := []plugintest.Option{ + plugintest.CaptureConfigureError(&err), + plugintest.CoreConfig(catalog.CoreConfig{ + TrustDomain: spiffeid.RequireTrustDomainFromString("example.org"), + }), + plugintest.ConfigureJSON(tt.config), + } + + newClient := func(awsConfig aws.Config) (rolesAnywhere, error) { + mockClient := fakeClient{ + t: t, + expectTrustAnchorID: aws.String(tt.config.TrustAnchorID), + updateTrustAnchorErr: tt.updateTrustAnchorErr, + } + return &mockClient, nil + } + p := newPlugin(newClient) + + if tt.config != nil { + plugintest.Load(t, builtin(p), nil, options...) + require.NoError(t, err) + } + + resp, err := p.PublishBundle(context.Background(), &bundlepublisherv1.PublishBundleRequest{ + Bundle: tt.bundle, + }) + + if tt.expectMsg != "" { + spiretest.RequireGRPCStatusContains(t, err, tt.expectCode, tt.expectMsg) + return + } + require.NoError(t, err) + require.NotNil(t, resp) + }) + } +} + +func TestPublishMultiple(t *testing.T) { + config := &Config{ + AccessKeyID: "access-key-id", + SecretAccessKey: "secret-access-key", + Region: "region", + TrustAnchorID: "trust-anchor-id", + } + + var err error + options := []plugintest.Option{ + plugintest.CaptureConfigureError(&err), + plugintest.CoreConfig(catalog.CoreConfig{ + TrustDomain: spiffeid.RequireTrustDomainFromString("example.org"), + }), + plugintest.ConfigureJSON(config), + } + + newClient := func(awsConfig aws.Config) (rolesAnywhere, error) { + return &fakeClient{ + t: t, + expectTrustAnchorID: aws.String(config.TrustAnchorID), + }, nil + } + p := newPlugin(newClient) + plugintest.Load(t, builtin(p), nil, options...) + require.NoError(t, err) + + // Test multiple update trust anchor operations, and check that only a call to + // UpdateTrustAnchor is made when there is a modified bundle that was not successfully + // published before. + + // Have an initial bundle with SequenceNumber = 1. + bundle := getTestBundle(t) + bundle.SequenceNumber = 1 + + client, ok := p.rolesAnywhereClient.(*fakeClient) + require.True(t, ok) + + // Reset the API call counters. + client.updateTrustAnchorCount = 0 + + // Throw an error when calling UpdateTrustAnchor. + client.updateTrustAnchorErr = errors.New("error calling UpdateTrustAnchor") + + // Call PublishBundle. UpdateTrustAnchor should be called and return an error. + resp, err := p.PublishBundle(context.Background(), &bundlepublisherv1.PublishBundleRequest{ + Bundle: bundle, + }) + require.Error(t, err) + require.Nil(t, resp) + + // The UpdateTrustAnchor call failed, so its counter should not be incremented. + require.Equal(t, 0, client.updateTrustAnchorCount) + + // Remove the updateTrustAnchorErr and try again. + client.updateTrustAnchorErr = nil + resp, err = p.PublishBundle(context.Background(), &bundlepublisherv1.PublishBundleRequest{ + Bundle: bundle, + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, 1, client.updateTrustAnchorCount) + + // Call PublishBundle with the same bundle. + resp, err = p.PublishBundle(context.Background(), &bundlepublisherv1.PublishBundleRequest{ + Bundle: bundle, + }) + require.NoError(t, err) + require.NotNil(t, resp) + + // The same bundle was used, the counter should be the same as before. + require.Equal(t, 1, client.updateTrustAnchorCount) + + // Have a new bundle and call PublishBundle. + bundle = getTestBundle(t) + bundle.SequenceNumber = 2 + resp, err = p.PublishBundle(context.Background(), &bundlepublisherv1.PublishBundleRequest{ + Bundle: bundle, + }) + require.NoError(t, err) + require.NotNil(t, resp) + + // PublishBundle was called with a different bundle, updateTrustAnchorCount should + // be incremented to be 3. + require.Equal(t, 2, client.updateTrustAnchorCount) + + // Try to publish a bundle that's too large, and expect that we receive an error. + bundle = getLargeTestBundle(t) + bundle.SequenceNumber = 3 + resp, err = p.PublishBundle(context.Background(), &bundlepublisherv1.PublishBundleRequest{ + Bundle: bundle, + }) + require.Nil(t, resp) + require.Error(t, err) +} + +type fakeClient struct { + t *testing.T + + awsConfig aws.Config + updateTrustAnchorErr error + updateTrustAnchorCount int + + expectTrustAnchorID *string +} + +func (c *fakeClient) UpdateTrustAnchor(_ context.Context, params *rolesanywhere.UpdateTrustAnchorInput, _ ...func(*rolesanywhere.Options)) (*rolesanywhere.UpdateTrustAnchorOutput, error) { + if c.updateTrustAnchorErr != nil { + return nil, c.updateTrustAnchorErr + } + + require.Equal(c.t, c.expectTrustAnchorID, params.TrustAnchorId, "trust anchor id mismatch") + trustAnchorArn := "trustAnchorArn" + trustAnchorName := "trustAnchorName" + c.updateTrustAnchorCount++ + return &rolesanywhere.UpdateTrustAnchorOutput{ + TrustAnchor: &rolesanywheretypes.TrustAnchorDetail{ + TrustAnchorArn: &trustAnchorArn, + Name: &trustAnchorName, + }, + }, nil +} + +func getTestBundle(t *testing.T) *types.Bundle { + cert, _, err := util.LoadCAFixture() + require.NoError(t, err) + + keyPkix, err := x509.MarshalPKIXPublicKey(cert.PublicKey) + require.NoError(t, err) + + return &types.Bundle{ + TrustDomain: "example.org", + X509Authorities: []*types.X509Certificate{{Asn1: cert.Raw}}, + JwtAuthorities: []*types.JWTKey{ + { + KeyId: "KID", + PublicKey: keyPkix, + }, + }, + RefreshHint: 1440, + SequenceNumber: 100, + } +} + +func getLargeTestBundle(t *testing.T) *types.Bundle { + largeBundle, err := util.LoadLargeBundleFixture() + require.NoError(t, err) + + return &types.Bundle{ + TrustDomain: "example.org", + X509Authorities: []*types.X509Certificate{{Asn1: largeBundle[0].Raw}}, + JwtAuthorities: []*types.JWTKey{}, + RefreshHint: 1440, + SequenceNumber: 101, + } +} diff --git a/pkg/server/plugin/bundlepublisher/awsrolesanywhere/client.go b/pkg/server/plugin/bundlepublisher/awsrolesanywhere/client.go new file mode 100644 index 0000000000..88c1e4ed46 --- /dev/null +++ b/pkg/server/plugin/bundlepublisher/awsrolesanywhere/client.go @@ -0,0 +1,33 @@ +package awsrolesanywhere + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/rolesanywhere" +) + +type rolesAnywhere interface { + UpdateTrustAnchor(ctx context.Context, params *rolesanywhere.UpdateTrustAnchorInput, optFns ...func(*rolesanywhere.Options)) (*rolesanywhere.UpdateTrustAnchorOutput, error) +} + +func newAWSConfig(ctx context.Context, c *Config) (aws.Config, error) { + cfg, err := config.LoadDefaultConfig(ctx, + config.WithRegion(c.Region), + ) + if err != nil { + return aws.Config{}, err + } + + if c.SecretAccessKey != "" && c.AccessKeyID != "" { + cfg.Credentials = credentials.NewStaticCredentialsProvider(c.AccessKeyID, c.SecretAccessKey, "") + } + + return cfg, nil +} + +func newRolesAnywhereClient(c aws.Config) (rolesAnywhere, error) { + return rolesanywhere.NewFromConfig(c), nil +} diff --git a/test/fixture/certs/large_bundle.der b/test/fixture/certs/large_bundle.der new file mode 100644 index 0000000000000000000000000000000000000000..648cdcaacf641cdb8ba08ea184e324d45fa79508 GIT binary patch literal 7803 zcmXqLk}Ef8l8s-$%*4pVBvM}Ue%|l%Up;Fk36$Nv?;y{abMw9dFB_*;n@8JsUPeZ4 zRt5thLjeOmHs(+kW*+vC)Z!8aXGa4$ab6=612Y3-14Cm=Ba0{?*A$6sAZN%AGzO@L zi-#>Zz|+mm)sWkO6C}VU%oG}I(8Q>OYz-qT19KB2KLb#li>Zl`k>P207E9pLW972f zgM00N=&YSk`+H`(;`|@yoy8Z4YW+C%^31(`5vxA))m$&hkmtU&@?^`~jstr2?NMR* z*Q5199Ky`mwCCSjy!60v!P^O|QYXcoRu$RecCDdJRh1#)C!^r!$Hn4T@9_z(jwmS3 znYQPJ;;;MNI(Oa04l2e^NV(Cm)ha)9qWgpdeuFowh(whyqfdMDW&%(mY#NGfDSOnqFcLf zB<2?6r0V4tr7vlct_I4M8#YN71L=HF&@=NiNvHXTxdyrU_=m?ky9R~CkMc%CVl)+u z=7-U;V6;>mtr13R!_nGsv^E^A4M%Ik(b{mdHXN-DM{C2;+HkZs9IXvUYs1mnaI`iY ztqn(O!_nGsv^E^A4YAdRs8eaklbgUfRN&+$BSZ9>g-^{~G(&D)4+?63l@osT*8=xH z*DtW`oZX^ns$g!sKl{^bja#{VtvrWa)|7cY+VtXNR;lM|o4w0?a?BRy<`_vZ>aw`5 z+9bQB-~0Kkb=3^>)NK3Zy1UMpE;Z&4W<2!ze8kCFT}8+F3KJVoB+YQke{Wp)Kf}oE z`?~!HB2H}-a9p}9_@dfD*M0d~*Nb)H13Z2v7~ekh&Qi{Ba$mdlTeDOl#*2s6$ChRI zo)ET`+41zN?7lS?6=w3weRiF>+}5kHE53N)<#j1{#gcWeG#A~^(|_{j{102d@OaVu rUo+U-R~-^PIN2qnvsplL+mu^7yo{q%W@U8MC9zFm63aU$a%Ca_-5Jlt literal 0 HcmV?d00001 diff --git a/test/util/cert_fixtures.go b/test/util/cert_fixtures.go index b5643f14f4..d615e68574 100644 --- a/test/util/cert_fixtures.go +++ b/test/util/cert_fixtures.go @@ -10,11 +10,12 @@ import ( ) var ( - svidPath = path.Join(ProjectRoot(), "test/fixture/certs/svid.pem") - svidKeyPath = path.Join(ProjectRoot(), "test/fixture/certs/svid_key.pem") - caPath = path.Join(ProjectRoot(), "test/fixture/certs/ca.pem") - caKeyPath = path.Join(ProjectRoot(), "test/fixture/certs/ca_key.pem") - bundlePath = path.Join(ProjectRoot(), "test/fixture/certs/bundle.der") + svidPath = path.Join(ProjectRoot(), "test/fixture/certs/svid.pem") + svidKeyPath = path.Join(ProjectRoot(), "test/fixture/certs/svid_key.pem") + caPath = path.Join(ProjectRoot(), "test/fixture/certs/ca.pem") + caKeyPath = path.Join(ProjectRoot(), "test/fixture/certs/ca_key.pem") + bundlePath = path.Join(ProjectRoot(), "test/fixture/certs/bundle.der") + largeBundlePath = path.Join(ProjectRoot(), "test/fixture/certs/large_bundle.der") ) // LoadCAFixture reads, parses, and returns the pre-defined CA fixture and key @@ -31,6 +32,10 @@ func LoadBundleFixture() ([]*x509.Certificate, error) { return LoadBundle(bundlePath) } +func LoadLargeBundleFixture() ([]*x509.Certificate, error) { + return LoadBundle(largeBundlePath) +} + // LoadCertAndKey reads and parses both a certificate and a private key at once func LoadCertAndKey(crtPath, keyPath string) (*x509.Certificate, *ecdsa.PrivateKey, error) { crt, err := LoadCert(crtPath)