diff --git a/docs/modules/azure.md b/docs/modules/azure.md index c0cc6ca014..a6fc800771 100644 --- a/docs/modules/azure.md +++ b/docs/modules/azure.md @@ -24,6 +24,7 @@ The Azure module exposes the following Go packages: !!! warning "EULA Acceptance" Due to licensing restrictions you are required to explicitly accept an End User License Agreement (EULA) for the EventHubs container image. This is facilitated through the `WithAcceptEULA` function. - [CosmosDB](#cosmosdb): `github.com/testcontainers/testcontainers-go/modules/azure/cosmosdb`. +- [Lowkey Vault](#lowkey-vault): `github.com/testcontainers/testcontainers-go/modules/azure/lowkeyvault`. [Creating a Azurite container](../../modules/azure/azurite/examples_test.go) inside_block:runAzuriteContainer @@ -353,3 +354,137 @@ Returns the connection string to connect to the CosmosDB container and an error, [Connect_CreateDatabase](../../modules/azure/cosmosdb/examples_test.go) inside_block:ExampleRun_connect + +## Lowkey Vault + +### Run function + +- Not available until the next release :material-tag: main + +The Lowkey Vault module exposes one entrypoint function to create the Lowkey Vault container, and this function receives three parameters: + +```golang +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) +``` + +- `context.Context`, the Go context. +- `string`, the Docker image to use. +- `testcontainers.ContainerCustomizer`, a variadic argument for passing options. + + +#### Image + +Use the second argument in the `Run` function to set a valid Docker image. +In example: `Run(context.Background(), "nagyesta/lowkey-vault:7.0.9-ubi10-minimal")`. + +### Container Options + +The Lowkey Vault container exposes two ports, one for the Key Vault API and one for the metadata endpoints such as the token endpoint. +Since the Key Vault API supports multiple vaults and selects the active vault based on the host authority of the request URL, the +container can be configured in two ways: + +#### Local mode + +- Not available until the next release :material-tag: main + +The default mode is to run the Key Vault container on localhost, meaning that both the Key Vault API and the metadata endpoints are +exposed using random host ports, and the container is accessible only from the host machine. The default vault is automatically created +and is made available using the host address. + +#### Network mode + +- Not available until the next release :material-tag: main + +To prepare the container for running in a network and making it accessible from other containers in the network, you can use the +`WithNetworkAlias` option. For example: +```golang +Run(ctx, "nagyesta/lowkey-vault:7.0.9-ubi10-minimal", + lowkeyvault.WithNetworkAlias("lowkey-vault", aNetwork), +) +``` + +### Container Methods + +The Lowkey Vault container exposes the following methods: + +#### ConnectionURL + +- Not available until the next release :material-tag: main + +Returns the connection URL to connect to the Key Vault API of the Lowkey Vault container and an error, passing the Go context and an +`accessMode` as parameters. The access mode can be either `lowkeyvault.Local` or `lowkeyvault.Network` depending on the mode you wish +to use to connect to the Key Vault API. + +#### IdentityEndpoint + +- Not available until the next release :material-tag: main + +Returns the URL pointing to the token endpoint of the Lowkey Vault container and an error, passing the Go context and an `accessMode` +as parameters. The access mode can be either `lowkeyvault.Local` or `lowkeyvault.Network` depending on the mode you wish to use to +access the token endpoint. This can be set as the value of the `IDENTITY_ENDPOINT` environment variable without modification on the +machine which will connect to the Lowkey Vault container. When using the Azure Key Vault SDK for Go, this will let you authenticate +with managed identities by using the `azidentity.NewDefaultAzureCredential(nil)` as a credential. + +#### IdentityHeader + +- Not available until the next release :material-tag: main + +Can return the string which must be set as the value of the `IDENTITY_HEADER` environment variable without modification on the +machine which will connect to the Lowkey Vault container. When using the Azure Key Vault SDK for Go, this will let you authenticate +with managed identities by using the `azidentity.NewDefaultAzureCredential(nil)` as a credential. + +#### Client + +- Not available until the next release :material-tag: main + +Returns a `http.Client` and requires the Go context as parameter. This method can be used to prepare a `http.Client` for connecting +to the Key Vault API of the Lowkey Vault container using a self-signed certificate. This is necessary since the Lowkey Vault container +uses a self-signed certificate by default. + +### Examples + +#### Use the Secrets API in Local mode + +In the following example, we are starting the Lowkey Vault container in Local mode, set a secret and retrieve it using the Key Vault Secrets API. + + +[Run Lowkey Vault Container in Local mode](../../modules/azure/lowkeyvault/examples_test.go) inside_block:createContainerWithLocalMode +[Create Client](../../modules/azure/lowkeyvault/examples_test.go) inside_block:prepareTheSecretClient +[Set and get a secret](../../modules/azure/lowkeyvault/examples_test.go) inside_block:setAndFetchTheSecret + + +#### Use the Keys API in Local mode + +In the following example, we are starting the Lowkey Vault container in Local mode, create a key and encrypt and decrypt a message with it. + + +[Run Lowkey Vault Container in Local mode](../../modules/azure/lowkeyvault/examples_test.go) inside_block:createContainerWithLocalMode +[Create Client](../../modules/azure/lowkeyvault/examples_test.go) inside_block:prepareTheKeyClient +[Create a key](../../modules/azure/lowkeyvault/examples_test.go) inside_block:createKey +[Encrypt a message with the key](../../modules/azure/lowkeyvault/examples_test.go) inside_block:encryptMessage +[Decrypt cipher text with the key](../../modules/azure/lowkeyvault/examples_test.go) inside_block:decryptCipherText + + +#### Use the Certificates API in Local mode + +In the following example, we are starting the Lowkey Vault container in Local mode, create a certificate using the Key Vault Certificates +API, and fetch the content of the certificate as a PKCS12 store using the Key Vault Secrets API. + + +[Run Lowkey Vault Container in Local mode](../../modules/azure/lowkeyvault/examples_test.go) inside_block:createContainerWithLocalMode +[Create Certificate Client](../../modules/azure/lowkeyvault/examples_test.go) inside_block:prepareTheCertClient +[Create a certificate](../../modules/azure/lowkeyvault/examples_test.go) inside_block:createCertificate +[Create Secret Client](../../modules/azure/lowkeyvault/examples_test.go) inside_block:prepareTheSecretClient +[Fetch the certificate details](../../modules/azure/lowkeyvault/examples_test.go) inside_block:fetchCertDetails + + +#### Use the Secrets API in Network mode + +In the following example, we are starting the Lowkey Vault container in Network mode and setting the parameters of a Go client +container which will be used to connect to the Key Vault API of the Lowkey Vault container in Network mode. + + +[Run Lowkey Vault Container in Network mode](../../modules/azure/lowkeyvault/examples_test.go) inside_block:createContainerWithNetwork +[Get the endpoint details in Network mode](../../modules/azure/lowkeyvault/examples_test.go) inside_block:obtainEndpointUrls +[Configure the client container](../../modules/azure/lowkeyvault/examples_test.go) inside_block:configureClient + \ No newline at end of file diff --git a/modules/azure/go.mod b/modules/azure/go.mod index dcabfc1a8d..980dc62788 100644 --- a/modules/azure/go.mod +++ b/modules/azure/go.mod @@ -5,25 +5,33 @@ go 1.24.0 toolchain go1.24.7 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.4.1 github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.8.0 + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.4.0 + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v1.0.0 github.com/docker/go-connections v0.6.0 github.com/stretchr/testify v1.11.1 github.com/testcontainers/testcontainers-go v0.40.0 github.com/testcontainers/testcontainers-go/modules/mssql v0.40.0 + golang.org/x/crypto v0.47.0 + software.sslmate.com/src/go-pkcs12 v0.7.0 ) require ( dario.cat/mergo v1.0.2 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect github.com/Azure/go-amqp v1.3.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect @@ -40,9 +48,11 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/klauspost/compress v1.18.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -55,6 +65,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // 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.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect @@ -68,10 +79,9 @@ require ( go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/modules/azure/go.sum b/modules/azure/go.sum index 2871a2aab3..ba975f9bf8 100644 --- a/modules/azure/go.sum +++ b/modules/azure/go.sum @@ -4,10 +4,12 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8af github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.4.1 h1:ToPLhnXvatKVN4ZkcxLOwcXOJhdu4iQl8w0efeuDz9Y= github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.4.1/go.mod h1:Krtog/7tz27z75TwM5cIS8bxEH4dcBUezcq+kGVeZEo= github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.3.0 h1:NnE8y/opvxowwNcSNHubQUiSSEhfk3dmooLGAOmPuKs= @@ -22,6 +24,14 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.3. github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.3.0/go.mod h1:TSH7DcFItwAufy0Lz+Ft2cyopExCpxbOxI5SkH4dRNo= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.4.0 h1:mtvR5ZXH5Ew6PSONd5lO5OXovWP1E3oAlgC8fpxor2Q= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.4.0/go.mod h1:u560+RFVfG0CBPzkXlDW43slESbBAQjgDGi3r6z+wk8= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI= github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v1.0.0 h1:lJwNFV+xYjHREUTHJKx/ZF6CJSt9znxmLw9DqSTvyRU= @@ -30,8 +40,10 @@ github.com/Azure/go-amqp v1.3.0 h1://1rikYhoIQNXJFXyoO/Rlb4+4EkHYfJceNtLlys2/4= github.com/Azure/go-amqp v1.3.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -74,9 +86,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= @@ -92,6 +103,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -172,20 +185,21 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -206,3 +220,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +software.sslmate.com/src/go-pkcs12 v0.7.0 h1:Db8W44cB54TWD7stUFFSWxdfpdn6fZVcDl0w3R4RVM0= +software.sslmate.com/src/go-pkcs12 v0.7.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/modules/azure/lowkeyvault/examples_test.go b/modules/azure/lowkeyvault/examples_test.go new file mode 100644 index 0000000000..67071a0114 --- /dev/null +++ b/modules/azure/lowkeyvault/examples_test.go @@ -0,0 +1,500 @@ +package lowkeyvault_test + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "encoding/base64" + "fmt" + "log" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" + "software.sslmate.com/src/go-pkcs12" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/azure/lowkeyvault" + "github.com/testcontainers/testcontainers-go/network" + "github.com/testcontainers/testcontainers-go/wait" +) + +func ExampleRun() { + ctx := context.Background() + + lowkeyVaultContainer, err := lowkeyvault.Run(ctx, "nagyesta/lowkey-vault:7.0.9-ubi10-minimal") + defer func() { + if err := testcontainers.TerminateContainer(lowkeyVaultContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + state, err := lowkeyVaultContainer.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.Running) + + // Output: + // true +} + +func ExampleRun_secretOperationsNetwork() { + ctx := context.Background() + + aNetwork, err := network.New(ctx) + defer func() { + err := aNetwork.Remove(ctx) + if err != nil { + log.Printf("failed to remove network: %s", err) + return + } + }() + if err != nil { + log.Printf("failed to setup network: %s", err) + return + } + + // createContainerWithNetwork { + lowkeyVaultContainer, err := lowkeyvault.Run(ctx, "nagyesta/lowkey-vault:7.0.9-ubi10-minimal", + lowkeyvault.WithNetworkAlias("lowkey-vault", aNetwork), + ) + defer func() { + if err := testcontainers.TerminateContainer(lowkeyVaultContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } + + // obtainEndpointUrls { + connURL, err := lowkeyVaultContainer.ConnectionURL(ctx, lowkeyvault.Network) + if err != nil { + log.Printf("failed to get connection url: %s", err) + return + } + + identityEndpoint, err := lowkeyVaultContainer.IdentityEndpoint(ctx, lowkeyvault.Network) + if err != nil { + log.Printf("failed to get token url: %s", err) + return + } + // } + + networkContainer, err := testcontainers.Run(ctx, "", + testcontainers.WithDockerfile( + testcontainers.FromDockerfile{ + Context: "testdata", + Dockerfile: "Dockerfile", + KeepImage: false, + }), + // configureClient { + testcontainers.WithEnv(map[string]string{ + "IDENTITY_ENDPOINT": identityEndpoint, + "IDENTITY_HEADER": lowkeyVaultContainer.IdentityHeader(), + "CONNECTION_URL": connURL, + }), + // } + network.WithNetwork(nil, aNetwork), + testcontainers.WithWaitStrategy( + wait.ForLog("true"), + ), + ) + defer func() { + if err := testcontainers.TerminateContainer(networkContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + state, err := networkContainer.State(ctx) + if err != nil { + log.Printf("failed to get container state: %s", err) + return + } + + fmt.Println(state.ExitCode == 0) + + // Output: + // true +} + +func ExampleRun_secretOperations() { + ctx := context.Background() + + // createContainerWithLocalMode { + lowkeyVaultContainer, err := lowkeyvault.Run(ctx, "nagyesta/lowkey-vault:7.0.9-ubi10-minimal") + defer func() { + if err := testcontainers.TerminateContainer(lowkeyVaultContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } + + // prepareTheSecretClient { + connURL, err := lowkeyVaultContainer.ConnectionURL(ctx, lowkeyvault.Local) + if err != nil { + log.Printf("failed to get connection url: %s", err) + return + } + + identityEndpoint, err := lowkeyVaultContainer.IdentityEndpoint(ctx, lowkeyvault.Local) + if err != nil { + log.Printf("failed to get identity endpoint: %s", err) + return + } + err = os.Setenv("IDENTITY_ENDPOINT", identityEndpoint) + if err != nil { + log.Printf("failed to set managed identity endpoint variable: %s", err) + return + } + err = os.Setenv("IDENTITY_HEADER", lowkeyVaultContainer.IdentityHeader()) + if err != nil { + log.Printf("failed to set managed identity header variable: %s", err) + return + } + + httpClient, err := lowkeyVaultContainer.Client(ctx) + if err != nil { + log.Printf("failed to get client: %s", err) + return + } + + cred, err := azidentity.NewDefaultAzureCredential(nil) // Will use Managed Identity via the Assumed Identity container + if err != nil { + log.Printf("failed to create credential: %v", err) + return + } + secretClient, err := azsecrets.NewClient(connURL, + cred, + &azsecrets.ClientOptions{ClientOptions: struct { + APIVersion string + Cloud cloud.Configuration + InsecureAllowCredentialWithHTTP bool + Logging policy.LogOptions + Retry policy.RetryOptions + Telemetry policy.TelemetryOptions + TracingProvider tracing.Provider + Transport policy.Transporter + PerCallPolicies []policy.Policy + PerRetryPolicies []policy.Policy + }{Transport: &httpClient}, DisableChallengeResourceVerification: true}) + if err != nil { + log.Printf("failed to create secret client: %v", err) + return + } + // } + + // setAndFetchTheSecret { + secretName := "secret-name" + secretValue := "a secret value" + created, err := secretClient.SetSecret(ctx, secretName, azsecrets.SetSecretParameters{Value: &secretValue}, nil) + if err != nil { + log.Printf("failed to set the secret %s", err.Error()) + return + } + + fetched, err := secretClient.GetSecret(ctx, secretName, created.ID.Version(), nil) + if err != nil { + log.Printf("failed to get the secret %s", err.Error()) + return + } + fetchedValue := *fetched.Value + // } + + fmt.Println(fetchedValue == secretValue) + + // Output: + // true +} + +func ExampleRun_keyOperations() { + ctx := context.Background() + + lowkeyVaultContainer, err := lowkeyvault.Run(ctx, "nagyesta/lowkey-vault:7.0.9-ubi10-minimal") + defer func() { + if err := testcontainers.TerminateContainer(lowkeyVaultContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + // prepareTheKeyClient { + connURL, err := lowkeyVaultContainer.ConnectionURL(ctx, lowkeyvault.Local) + if err != nil { + log.Printf("failed to get connection url: %s", err) + return + } + + identityEndpoint, err := lowkeyVaultContainer.IdentityEndpoint(ctx, lowkeyvault.Local) + if err != nil { + log.Printf("failed to get identity endpoint: %s", err) + return + } + err = os.Setenv("IDENTITY_ENDPOINT", identityEndpoint) + if err != nil { + log.Printf("failed to set managed identity endpoint variable: %s", err) + return + } + err = os.Setenv("IDENTITY_HEADER", lowkeyVaultContainer.IdentityHeader()) + if err != nil { + log.Printf("failed to set managed identity header variable: %s", err) + return + } + + httpClient, err := lowkeyVaultContainer.Client(ctx) + if err != nil { + log.Printf("failed to get client: %s", err) + return + } + + cred, err := azidentity.NewDefaultAzureCredential(nil) // Will use Managed Identity via the Assumed Identity container + if err != nil { + log.Printf("failed to create credential: %v", err) + return + } + keyClient, err := azkeys.NewClient(connURL, + cred, + &azkeys.ClientOptions{ClientOptions: struct { + APIVersion string + Cloud cloud.Configuration + InsecureAllowCredentialWithHTTP bool + Logging policy.LogOptions + Retry policy.RetryOptions + Telemetry policy.TelemetryOptions + TracingProvider tracing.Provider + Transport policy.Transporter + PerCallPolicies []policy.Policy + PerRetryPolicies []policy.Policy + }{Transport: &httpClient}, DisableChallengeResourceVerification: true}) + if err != nil { + log.Printf("failed to create key client: %v", err) + return + } + // } + + // createKey { + keyName := "rsa-key" + rsaKeyParams := azkeys.CreateKeyParameters{ + Kty: to.Ptr(azkeys.KeyTypeRSA), + KeySize: to.Ptr(int32(2048)), + KeyOps: []*azkeys.KeyOperation{ + to.Ptr(azkeys.KeyOperationDecrypt), + to.Ptr(azkeys.KeyOperationEncrypt), + to.Ptr(azkeys.KeyOperationUnwrapKey), + to.Ptr(azkeys.KeyOperationWrapKey), + }, + } + createdKey, err := keyClient.CreateKey(ctx, keyName, rsaKeyParams, nil) + if err != nil { + log.Printf("failed to create a key: %v", err) + return + } + // } + + // encryptMessage { + secretMessage := "a secret message" + encryptionParameters := azkeys.KeyOperationParameters{ + Value: []byte(secretMessage), + Algorithm: to.Ptr(azkeys.EncryptionAlgorithmRSAOAEP256), + } + encrResp, err := keyClient.Encrypt(ctx, keyName, createdKey.Key.KID.Version(), encryptionParameters, nil) + if err != nil { + log.Printf("failed to encrypt a message: %v", err) + return + } + cipherText := encrResp.Result + // } + + // decryptCipherText { + decryptionParameters := azkeys.KeyOperationParameters{ + Value: cipherText, + Algorithm: to.Ptr(azkeys.EncryptionAlgorithmRSAOAEP256), + } + decrResp, err := keyClient.Decrypt(ctx, keyName, createdKey.Key.KID.Version(), decryptionParameters, nil) + if err != nil { + log.Printf("failed to decrypt a message: %v", err) + return + } + decryptedMessage := string(decrResp.Result) + // } + + fmt.Println(decryptedMessage == secretMessage) + + // Output: + // true +} + +func ExampleRun_certificateOperations() { + ctx := context.Background() + + lowkeyVaultContainer, err := lowkeyvault.Run(ctx, "nagyesta/lowkey-vault:7.0.9-ubi10-minimal") + defer func() { + if err := testcontainers.TerminateContainer(lowkeyVaultContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + // prepareTheCertClient { + connURL, err := lowkeyVaultContainer.ConnectionURL(ctx, lowkeyvault.Local) + if err != nil { + log.Printf("failed to get connection url: %s", err) + return + } + + identityEndpoint, err := lowkeyVaultContainer.IdentityEndpoint(ctx, lowkeyvault.Local) + if err != nil { + log.Printf("failed to get identity endpoint: %s", err) + return + } + err = os.Setenv("IDENTITY_ENDPOINT", identityEndpoint) + if err != nil { + log.Printf("failed to set managed identity endpoint variable: %s", err) + return + } + err = os.Setenv("IDENTITY_HEADER", lowkeyVaultContainer.IdentityHeader()) + if err != nil { + log.Printf("failed to set managed identity header variable: %s", err) + return + } + + httpClient, err := lowkeyVaultContainer.Client(ctx) + if err != nil { + log.Printf("failed to get client: %s", err) + return + } + + cred, err := azidentity.NewDefaultAzureCredential(nil) // Will use Managed Identity via the Assumed Identity container + if err != nil { + log.Printf("failed to create credential: %v", err) + return + } + certClient, err := azcertificates.NewClient(connURL, + cred, + &azcertificates.ClientOptions{ClientOptions: struct { + APIVersion string + Cloud cloud.Configuration + InsecureAllowCredentialWithHTTP bool + Logging policy.LogOptions + Retry policy.RetryOptions + Telemetry policy.TelemetryOptions + TracingProvider tracing.Provider + Transport policy.Transporter + PerCallPolicies []policy.Policy + PerRetryPolicies []policy.Policy + }{Transport: &httpClient}, DisableChallengeResourceVerification: true}) + if err != nil { + log.Printf("failed to create certificate client: %v", err) + return + } + // } + + // createCertificate { + certName := "ec-cert" + subject := "CN=example.com" + _, err = certClient.CreateCertificate(ctx, certName, azcertificates.CreateCertificateParameters{ + CertificatePolicy: &azcertificates.CertificatePolicy{ + IssuerParameters: &azcertificates.IssuerParameters{ + Name: to.Ptr("Self"), + }, + KeyProperties: &azcertificates.KeyProperties{ + Curve: to.Ptr(azcertificates.CurveNameP256), + KeyType: to.Ptr(azcertificates.KeyTypeEC), + ReuseKey: to.Ptr(true), + }, + SecretProperties: &azcertificates.SecretProperties{ + ContentType: to.Ptr("application/x-pkcs12"), + }, + X509CertificateProperties: &azcertificates.X509CertificateProperties{ + Subject: &subject, + SubjectAlternativeNames: &azcertificates.SubjectAlternativeNames{ + DNSNames: []*string{to.Ptr("localhost")}, + }, + ValidityInMonths: to.Ptr(int32(12)), + }, + }, + }, nil) + if err != nil { + log.Printf("failed to create a certificate: %v", err) + return + } + // } + + secretClient, err := azsecrets.NewClient(connURL, + cred, + &azsecrets.ClientOptions{ClientOptions: struct { + APIVersion string + Cloud cloud.Configuration + InsecureAllowCredentialWithHTTP bool + Logging policy.LogOptions + Retry policy.RetryOptions + Telemetry policy.TelemetryOptions + TracingProvider tracing.Provider + Transport policy.Transporter + PerCallPolicies []policy.Policy + PerRetryPolicies []policy.Policy + }{Transport: &httpClient}, DisableChallengeResourceVerification: true}) + if err != nil { + log.Printf("failed to create secret client: %v", err) + return + } + + // fetchCertDetails { + base64Secret, err := secretClient.GetSecret(ctx, certName, "", nil) + if err != nil { + log.Printf("failed to get the secret with certificate store: %v", err) + return + } + base64Value := *base64Secret.Value + bytes, err := base64.StdEncoding.DecodeString(base64Value) + if err != nil { + log.Printf("failed to decode the certificate store: %v", err) + return + } + // use SSLMate library to decode the certificate store as the x/crypto + // library is not fully compatible with the Java PKCS12 format + key, cert, err := pkcs12.Decode(bytes, "") + if err != nil { + log.Printf("failed to open certificate store: %v", err) + return + } + ecKey, ok := key.(*ecdsa.PrivateKey) + if !ok { + log.Printf("unexpected key type: %T", key) + return + } + // } + + fmt.Println(cert.Subject.String() == subject && ecKey.Curve == elliptic.P256()) + + // Output: + // true +} diff --git a/modules/azure/lowkeyvault/lowkeyvault.go b/modules/azure/lowkeyvault/lowkeyvault.go new file mode 100644 index 0000000000..689a96a484 --- /dev/null +++ b/modules/azure/lowkeyvault/lowkeyvault.go @@ -0,0 +1,230 @@ +package lowkeyvault + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io" + "net/http" + + "github.com/docker/go-connections/nat" + "golang.org/x/crypto/pkcs12" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/network" + "github.com/testcontainers/testcontainers-go/wait" +) + +// possible access modes +const ( + Local = iota + Network +) + +const ( + // defaultAPIPort is the default port used by for the Lowkey Vault Key Vault API endpoints + defaultAPIPort nat.Port = "8443/tcp" + // defaultMetadataPort is the default port used for the Lowkey Vault Metadata endpoints + defaultMetadataPort nat.Port = "8080/tcp" +) + +// Container represents the Lowkey Vault container type used in the module +type Container struct { + testcontainers.Container + localHostName string + remoteHostName string +} + +// WithNetworkAlias sets the alias of the container for the provided network and adds the specified name as a key vault alias for the default, "localhost", vault. +func WithNetworkAlias(alias string, forNetwork *testcontainers.DockerNetwork) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + err := network.WithNetwork([]string{alias}, forNetwork).Customize(req) + if err != nil { + return err + } + // The placeholder will be replaced by the container automatically just in time + envs := map[string]string{"LOWKEY_VAULT_ALIASES": "localhost=" + alias + ":"} + err = testcontainers.WithEnv(envs).Customize(req) + if err != nil { + return err + } + return nil + } +} + +// Run creates an instance of the Lowkey Vault container type +func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) { + // Initialize with module defaults + moduleOpts := []testcontainers.ContainerCustomizer{ + testcontainers.WithExposedPorts(defaultAPIPort.Port(), defaultMetadataPort.Port()), + testcontainers.WithEnv(map[string]string{ + "LOWKEY_VAULT_RELAXED_PORTS": "true", + }), + testcontainers.WithWaitStrategy( + wait.ForLog("Started LowkeyVaultApp in "), + ), + } + + // Add user-provided options + moduleOpts = append(moduleOpts, opts...) + + ctr, err := testcontainers.Run(ctx, img, moduleOpts...) + var c *Container + if ctr != nil { + c = &Container{Container: ctr, localHostName: "", remoteHostName: ""} + } + + if err != nil { + return c, fmt.Errorf("run lowkeyvault: %w", err) + } + + return c, nil +} + +// Client prepares a client which will accept insecure (self-signed) certificates. +func (c *Container) Client(ctx context.Context) (http.Client, error) { + customTransport := http.DefaultTransport.(*http.Transport).Clone() + authority, err := c.mappedHostAuthority(ctx, defaultMetadataPort, Local) + if err != nil { + return http.Client{}, fmt.Errorf("host authority: %w", err) + } + pass, err := c.fetchDefaultCertPassword(authority) + if err != nil { + return http.Client{}, fmt.Errorf("default cert password: %w", err) + } + p12Data, err := c.fetchDefaultCertContent(authority) + if err != nil { + return http.Client{}, fmt.Errorf("default cert content: %w", err) + } + + _, cert, err := pkcs12.Decode(p12Data, pass) + if err != nil { + return http.Client{}, fmt.Errorf("decode cert content: %w", err) + } + + certPool := x509.NewCertPool() + certPool.AddCert(cert) + + customTransport.TLSClientConfig = &tls.Config{RootCAs: certPool} + return http.Client{Transport: customTransport}, nil +} + +func (c *Container) fetchDefaultCertPassword(authority string) (string, error) { + bytes, err := c.fetchContent("http://" + authority + "/metadata/default-cert/password") + if err != nil { + return "", fmt.Errorf("default cert password: %w", err) + } + return string(bytes), nil +} + +func (c *Container) fetchDefaultCertContent(authority string) ([]byte, error) { + return c.fetchContent("http://" + authority + "/metadata/default-cert/lowkey-vault.p12") +} + +func (c *Container) fetchContent(url string) ([]byte, error) { + resp, err := http.Get(url) + if resp != nil { + defer resp.Body.Close() + } + if err != nil { + return nil, fmt.Errorf("get content: %w", err) + } + + if resp != nil && resp.StatusCode == http.StatusOK { + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + return bodyBytes, nil + } + + return nil, errors.New("content not found") +} + +// ConnectionURL returns the connection URL for the Lowkey Vault API based on the provided access mode. +func (c *Container) ConnectionURL(ctx context.Context, accessMode int) (string, error) { + hostAuthority, err := c.mappedHostAuthority(ctx, defaultAPIPort, accessMode) + if err != nil { + return "", fmt.Errorf("host authority: %w", err) + } + + return "https://" + hostAuthority, nil +} + +// IdentityEndpoint returns the URL value of the IDENTITY_ENDPOINT environment variable for the managed identity simulation. This will be used to obtain an access token for the Lowkey Vault API. +func (c *Container) IdentityEndpoint(ctx context.Context, accessMode int) (string, error) { + hostAuthority, err := c.mappedHostAuthority(ctx, defaultMetadataPort, accessMode) + if err != nil { + return "", fmt.Errorf("host authority: %w", err) + } + return fmt.Sprintf("http://%s/metadata/identity/oauth2/token", hostAuthority), nil +} + +// IdentityHeader returns the value of the IDENTITY_HEADER environment variable for the managed identity simulation. +func (c *Container) IdentityHeader() string { + return "header" +} + +func (c *Container) mappedHostAuthority(ctx context.Context, exposedPort nat.Port, accessMode int) (string, error) { + switch accessMode { + case Local: + host, err := c.resolveLocalHostName(ctx) + if err != nil { + return "", fmt.Errorf("host: %w", err) + } + port, err := c.MappedPort(ctx, exposedPort) + if err != nil { + return "", fmt.Errorf("port: %w", err) + } + return fmt.Sprintf("%s:%d", host, port.Int()), nil + case Network: + host, err := c.resolveNetworkHostName(ctx) + if err != nil { + return "", fmt.Errorf("host: %w", err) + } + return fmt.Sprintf("%s:%d", host, exposedPort.Int()), nil + default: + return "", fmt.Errorf("unsupported access mode: %d", accessMode) + } +} + +func (c *Container) resolveLocalHostName(ctx context.Context) (string, error) { + if c.localHostName != "" { + return c.localHostName, nil + } + host, err := c.Host(ctx) + if err != nil { + return "", fmt.Errorf("host: %w", err) + } + c.localHostName = host + return host, nil +} + +func (c *Container) resolveNetworkHostName(ctx context.Context) (string, error) { + if c.remoteHostName != "" { + return c.remoteHostName, nil + } + networks, err := c.Networks(ctx) + if err != nil { + return "", fmt.Errorf("networks: %w", err) + } + if len(networks) != 1 { + return "", fmt.Errorf("the container must have exactly one network, but it has %d", len(networks)) + } + hosts, err := c.NetworkAliases(ctx) + if err != nil { + return "", fmt.Errorf("network aliases: %w", err) + } + if len(hosts) == 0 { + return "", errors.New("no network aliases found in the Lowkey Vault container") + } + aliases := hosts[networks[0]] + if len(aliases) != 1 { + return "", fmt.Errorf("the container must have exactly one network alias, but it has %d", len(aliases)) + } + host := aliases[0] + c.remoteHostName = host + return host, nil +} diff --git a/modules/azure/lowkeyvault/lowkeyvault_test.go b/modules/azure/lowkeyvault/lowkeyvault_test.go new file mode 100644 index 0000000000..8516deab7f --- /dev/null +++ b/modules/azure/lowkeyvault/lowkeyvault_test.go @@ -0,0 +1,306 @@ +package lowkeyvault_test + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "encoding/base64" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" + "github.com/stretchr/testify/require" + "software.sslmate.com/src/go-pkcs12" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/azure/lowkeyvault" + "github.com/testcontainers/testcontainers-go/network" + "github.com/testcontainers/testcontainers-go/wait" +) + +func TestRun(t *testing.T) { + ctx := context.Background() + + lowkeyVaultContainer, err := lowkeyvault.Run(ctx, "nagyesta/lowkey-vault:7.0.9-ubi10-minimal") + testcontainers.CleanupContainer(t, lowkeyVaultContainer) + require.NoError(t, err) + + state, err := lowkeyVaultContainer.State(ctx) + require.NoError(t, err) + + require.True(t, state.Running) +} + +func TestRun_secretOperationsNetwork(t *testing.T) { + ctx := context.Background() + + aNetwork, err := network.New(ctx) + testcontainers.CleanupNetwork(t, aNetwork) + require.NoError(t, err) + + lowkeyVaultContainer, err := lowkeyvault.Run(ctx, "nagyesta/lowkey-vault:7.0.9-ubi10-minimal", + lowkeyvault.WithNetworkAlias("lowkey-vault", aNetwork), + ) + testcontainers.CleanupContainer(t, lowkeyVaultContainer) + require.NoError(t, err) + + connURL, err := lowkeyVaultContainer.ConnectionURL(ctx, lowkeyvault.Network) + require.NoError(t, err) + require.NotNil(t, connURL) + + tokenURL, err := lowkeyVaultContainer.IdentityEndpoint(ctx, lowkeyvault.Network) + require.NoError(t, err) + require.NotNil(t, tokenURL) + + networkContainer, err := testcontainers.Run(ctx, "", + testcontainers.WithDockerfile( + testcontainers.FromDockerfile{ + Context: "testdata", + Dockerfile: "Dockerfile", + KeepImage: false, + }), + testcontainers.WithEnv(map[string]string{ + "IDENTITY_ENDPOINT": tokenURL, + "IDENTITY_HEADER": lowkeyVaultContainer.IdentityHeader(), + "CONNECTION_URL": connURL, + }), + network.WithNetwork(nil, aNetwork), + testcontainers.WithWaitStrategy( + wait.ForLog("true"), + ), + ) + testcontainers.CleanupContainer(t, networkContainer) + require.NoError(t, err) + require.NotNil(t, networkContainer) + state, err := networkContainer.State(ctx) + require.NoError(t, err) + + require.Equal(t, 0, state.ExitCode) +} + +func TestRun_secretOperations(t *testing.T) { + ctx := context.Background() + + lowkeyVaultContainer, err := lowkeyvault.Run(ctx, "nagyesta/lowkey-vault:7.0.9-ubi10-minimal") + testcontainers.CleanupContainer(t, lowkeyVaultContainer) + require.NoError(t, err) + + connURL, err := lowkeyVaultContainer.ConnectionURL(ctx, lowkeyvault.Local) + require.NoError(t, err) + require.NotNil(t, connURL) + + identityEndpoint, err := lowkeyVaultContainer.IdentityEndpoint(ctx, lowkeyvault.Local) + require.NoError(t, err) + t.Setenv("IDENTITY_ENDPOINT", identityEndpoint) + t.Setenv("IDENTITY_HEADER", lowkeyVaultContainer.IdentityHeader()) + + httpClient, err := lowkeyVaultContainer.Client(ctx) + require.NoError(t, err) + + cred, err := azidentity.NewDefaultAzureCredential(nil) // Will use Managed Identity via the Assumed Identity container + require.NoError(t, err) + secretClient, err := azsecrets.NewClient(connURL, + cred, + &azsecrets.ClientOptions{ClientOptions: struct { + APIVersion string + Cloud cloud.Configuration + InsecureAllowCredentialWithHTTP bool + Logging policy.LogOptions + Retry policy.RetryOptions + Telemetry policy.TelemetryOptions + TracingProvider tracing.Provider + Transport policy.Transporter + PerCallPolicies []policy.Policy + PerRetryPolicies []policy.Policy + }{Transport: &httpClient}, DisableChallengeResourceVerification: true}) + require.NoError(t, err) + + secretName := "secret-name" + secretValue := "a secret value" + created, err := secretClient.SetSecret(ctx, secretName, azsecrets.SetSecretParameters{Value: &secretValue}, nil) + require.NoError(t, err) + require.NotNil(t, created) + + fetched, err := secretClient.GetSecret(ctx, secretName, created.ID.Version(), nil) + require.NoError(t, err) + require.NotNil(t, fetched) + fetchedValue := *fetched.Value + + require.Equal(t, secretValue, fetchedValue) +} + +func TestRun_keyOperations(t *testing.T) { + ctx := context.Background() + + lowkeyVaultContainer, err := lowkeyvault.Run(ctx, "nagyesta/lowkey-vault:7.0.9-ubi10-minimal") + testcontainers.CleanupContainer(t, lowkeyVaultContainer) + require.NoError(t, err) + + connURL, err := lowkeyVaultContainer.ConnectionURL(ctx, lowkeyvault.Local) + require.NoError(t, err) + require.NotNil(t, connURL) + + identityEndpoint, err := lowkeyVaultContainer.IdentityEndpoint(ctx, lowkeyvault.Local) + require.NoError(t, err) + t.Setenv("IDENTITY_ENDPOINT", identityEndpoint) + t.Setenv("IDENTITY_HEADER", lowkeyVaultContainer.IdentityHeader()) + + httpClient, err := lowkeyVaultContainer.Client(ctx) + require.NoError(t, err) + + cred, err := azidentity.NewDefaultAzureCredential(nil) // Will use Managed Identity via the Assumed Identity container + require.NoError(t, err) + keyClient, err := azkeys.NewClient(connURL, + cred, + &azkeys.ClientOptions{ClientOptions: struct { + APIVersion string + Cloud cloud.Configuration + InsecureAllowCredentialWithHTTP bool + Logging policy.LogOptions + Retry policy.RetryOptions + Telemetry policy.TelemetryOptions + TracingProvider tracing.Provider + Transport policy.Transporter + PerCallPolicies []policy.Policy + PerRetryPolicies []policy.Policy + }{Transport: &httpClient}, DisableChallengeResourceVerification: true}) + require.NoError(t, err) + + keyName := "rsa-key" + rsaKeyParams := azkeys.CreateKeyParameters{ + Kty: to.Ptr(azkeys.KeyTypeRSA), + KeySize: to.Ptr(int32(2048)), + KeyOps: []*azkeys.KeyOperation{ + to.Ptr(azkeys.KeyOperationDecrypt), + to.Ptr(azkeys.KeyOperationEncrypt), + to.Ptr(azkeys.KeyOperationUnwrapKey), + to.Ptr(azkeys.KeyOperationWrapKey), + }, + } + createdKey, err := keyClient.CreateKey(ctx, keyName, rsaKeyParams, nil) + require.NoError(t, err) + require.NotNil(t, createdKey) + + secretMessage := "a secret message" + encryptionParameters := azkeys.KeyOperationParameters{ + Value: []byte(secretMessage), + Algorithm: to.Ptr(azkeys.EncryptionAlgorithmRSAOAEP256), + } + encrResp, err := keyClient.Encrypt(ctx, keyName, createdKey.Key.KID.Version(), encryptionParameters, nil) + require.NoError(t, err) + require.NotNil(t, encrResp) + cipherText := encrResp.Result + + decryptionParameters := azkeys.KeyOperationParameters{ + Value: cipherText, + Algorithm: to.Ptr(azkeys.EncryptionAlgorithmRSAOAEP256), + } + decrResp, err := keyClient.Decrypt(ctx, keyName, createdKey.Key.KID.Version(), decryptionParameters, nil) + require.NoError(t, err) + require.NotNil(t, decrResp) + decryptedMessage := string(decrResp.Result) + + require.Equal(t, secretMessage, decryptedMessage) +} + +func TestRun_certificateOperations(t *testing.T) { + ctx := context.Background() + + lowkeyVaultContainer, err := lowkeyvault.Run(ctx, "nagyesta/lowkey-vault:7.0.9-ubi10-minimal") + testcontainers.CleanupContainer(t, lowkeyVaultContainer) + require.NoError(t, err) + + connURL, err := lowkeyVaultContainer.ConnectionURL(ctx, lowkeyvault.Local) + require.NoError(t, err) + require.NotNil(t, connURL) + + identityEndpoint, err := lowkeyVaultContainer.IdentityEndpoint(ctx, lowkeyvault.Local) + require.NoError(t, err) + t.Setenv("IDENTITY_ENDPOINT", identityEndpoint) + t.Setenv("IDENTITY_HEADER", lowkeyVaultContainer.IdentityHeader()) + + httpClient, err := lowkeyVaultContainer.Client(ctx) + require.NoError(t, err) + + cred, err := azidentity.NewDefaultAzureCredential(nil) // Will use Managed Identity via the Assumed Identity container + require.NoError(t, err) + certClient, err := azcertificates.NewClient(connURL, + cred, + &azcertificates.ClientOptions{ClientOptions: struct { + APIVersion string + Cloud cloud.Configuration + InsecureAllowCredentialWithHTTP bool + Logging policy.LogOptions + Retry policy.RetryOptions + Telemetry policy.TelemetryOptions + TracingProvider tracing.Provider + Transport policy.Transporter + PerCallPolicies []policy.Policy + PerRetryPolicies []policy.Policy + }{Transport: &httpClient}, DisableChallengeResourceVerification: true}) + require.NoError(t, err) + + certName := "ec-cert" + subject := "CN=example.com" + created, err := certClient.CreateCertificate(ctx, certName, azcertificates.CreateCertificateParameters{ + CertificatePolicy: &azcertificates.CertificatePolicy{ + IssuerParameters: &azcertificates.IssuerParameters{ + Name: to.Ptr("Self"), + }, + KeyProperties: &azcertificates.KeyProperties{ + Curve: to.Ptr(azcertificates.CurveNameP256), + KeyType: to.Ptr(azcertificates.KeyTypeEC), + ReuseKey: to.Ptr(true), + }, + SecretProperties: &azcertificates.SecretProperties{ + ContentType: to.Ptr("application/x-pkcs12"), + }, + X509CertificateProperties: &azcertificates.X509CertificateProperties{ + Subject: &subject, + SubjectAlternativeNames: &azcertificates.SubjectAlternativeNames{ + DNSNames: []*string{to.Ptr("localhost")}, + }, + ValidityInMonths: to.Ptr(int32(12)), + }, + }, + }, nil) + require.NoError(t, err) + require.NotNil(t, created) + + secretClient, err := azsecrets.NewClient(connURL, + cred, + &azsecrets.ClientOptions{ClientOptions: struct { + APIVersion string + Cloud cloud.Configuration + InsecureAllowCredentialWithHTTP bool + Logging policy.LogOptions + Retry policy.RetryOptions + Telemetry policy.TelemetryOptions + TracingProvider tracing.Provider + Transport policy.Transporter + PerCallPolicies []policy.Policy + PerRetryPolicies []policy.Policy + }{Transport: &httpClient}, DisableChallengeResourceVerification: true}) + require.NoError(t, err) + + base64Secret, err := secretClient.GetSecret(ctx, certName, "", nil) + require.NoError(t, err) + require.NotNil(t, base64Secret) + base64Value := *base64Secret.Value + bytes, err := base64.StdEncoding.DecodeString(base64Value) + require.NoError(t, err) + // use SSLMate library to decode the certificate store as the x/crypto + // library is not fully compatible with the Java PKCS12 format + key, cert, err := pkcs12.Decode(bytes, "") + require.NoError(t, err) + ecKey := key.(*ecdsa.PrivateKey) + + require.Equal(t, subject, cert.Subject.String()) + require.Equal(t, elliptic.P256(), ecKey.Curve) +} diff --git a/modules/azure/lowkeyvault/testdata/Dockerfile b/modules/azure/lowkeyvault/testdata/Dockerfile new file mode 100644 index 0000000000..d0980b51d0 --- /dev/null +++ b/modules/azure/lowkeyvault/testdata/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.24-alpine@sha256:fc2cff6625f3c1c92e6c85938ac5bd09034ad0d4bc2dfb08278020b68540dbb5 as builder +WORKDIR /app +COPY . . +RUN mkdir -p dist +RUN go build -o ./dist/server main.go + +FROM alpine +WORKDIR /app +COPY --from=builder /app/dist/server . +EXPOSE 6443 +CMD ["/app/server"] diff --git a/modules/azure/lowkeyvault/testdata/go.mod b/modules/azure/lowkeyvault/testdata/go.mod new file mode 100644 index 0000000000..db119a2f1f --- /dev/null +++ b/modules/azure/lowkeyvault/testdata/go.mod @@ -0,0 +1,25 @@ +module lowkeyvault_network_test + +go 1.24.0 + +toolchain go1.24.7 + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect +) diff --git a/modules/azure/lowkeyvault/testdata/go.sum b/modules/azure/lowkeyvault/testdata/go.sum new file mode 100644 index 0000000000..f5af262ba7 --- /dev/null +++ b/modules/azure/lowkeyvault/testdata/go.sum @@ -0,0 +1,43 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/azure/lowkeyvault/testdata/main.go b/modules/azure/lowkeyvault/testdata/main.go new file mode 100644 index 0000000000..7cd36857b7 --- /dev/null +++ b/modules/azure/lowkeyvault/testdata/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "context" + "crypto/tls" + "fmt" + "log" + "net/http" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" +) + +func run() error { + ctx := context.Background() + + connUrl := os.Getenv("CONNECTION_URL") + log.Printf("Using Lowkey Vault endpoint: %s", connUrl) + tokenUrl := os.Getenv("IDENTITY_ENDPOINT") + log.Printf("Using token URL: %s", tokenUrl) + + customTransport := http.DefaultTransport.(*http.Transport).Clone() + customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + httpClient := http.Client{Transport: customTransport} + + resp, err := httpClient.Get(tokenUrl + "?resource=" + connUrl) + if err != nil { + log.Fatalf("failed to get token from token URL %v", err) + return err + } + err = resp.Body.Close() + if err != nil { + log.Fatalf("failed to get token from token URL 2 %v", err) + return err + } + + cred, err := azidentity.NewDefaultAzureCredential(nil) // Will use Managed Identity via the Assumed Identity container + if err != nil { + log.Fatalf("failed to create credential: %v", err) + return err + } + secretClient, err := azsecrets.NewClient(connUrl, + cred, + &azsecrets.ClientOptions{ClientOptions: struct { + APIVersion string + Cloud cloud.Configuration + InsecureAllowCredentialWithHTTP bool + Logging policy.LogOptions + Retry policy.RetryOptions + Telemetry policy.TelemetryOptions + TracingProvider tracing.Provider + Transport policy.Transporter + PerCallPolicies []policy.Policy + PerRetryPolicies []policy.Policy + }{Transport: &httpClient}, DisableChallengeResourceVerification: true}) + if err != nil { + log.Fatalf("failed to create secret client: %v", err) + return err + } + + secretName := "secret-name" + secretValue := "a secret value" + created, err := secretClient.SetSecret(ctx, secretName, azsecrets.SetSecretParameters{Value: &secretValue}, nil) + if err != nil { + log.Fatalf("failed to set the secret %v", err) + return err + } + + fetched, err := secretClient.GetSecret(ctx, secretName, created.ID.Version(), nil) + if err != nil { + log.Fatalf("failed to get the secret %v", err) + return err + } + fetchedValue := *fetched.Secret.Value + + fmt.Println(fetchedValue == secretValue) + + return nil +} + +func main() { + if err := run(); err != nil { + log.Fatal(err) + } +}