| 1 | import * as GCP from "@/GCP"; |
| 2 | import * as Test from "@/Test/Alchemy"; |
| 3 | import * as certificatemanager from "@distilled.cloud/gcp/certificatemanager_v1"; |
| 4 | import { expect } from "alchemy-test"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | import { MinimumLogLevel } from "effect/References"; |
| 7 | import * as Schedule from "effect/Schedule"; |
| 8 | |
| 9 | const { test } = Test.make({ providers: GCP.providers() }); |
| 10 | |
| 11 | const logLevel = Effect.provideService( |
| 12 | MinimumLogLevel, |
| 13 | process.env.DEBUG ? "Debug" : "Info", |
| 14 | ); |
| 15 | |
| 16 | // Self-signed RSA-2048 fixtures generated once with openssl (not at test time). |
| 17 | const CERT_A = `-----BEGIN CERTIFICATE----- |
| 18 | MIIDOjCCAiKgAwIBAgIUY07uz+vKYeBK3NYMhk03eWyTPKkwDQYJKoZIhvcNAQEL |
| 19 | BQAwHTEbMBkGA1UEAwwSYWxjaGVteS1zc2wtYS50ZXN0MB4XDTI2MDgyNDE1NDM0 |
| 20 | NFoXDTM2MDgyMTE1NDM0NFowHTEbMBkGA1UEAwwSYWxjaGVteS1zc2wtYS50ZXN0 |
| 21 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzUAYVVO5iOluteYkxOqA |
| 22 | bBF+EnWrtfsUMoPJPXBEIrmT66G+vx+rArDsRxJqnWr9YstEQKsHtkErGF37avZr |
| 23 | 12jTIiKWk9auxyhN8O6WD2jND13m+IfsdeWKSIFx3j/RJ0McJQYu4YuN8ViZm3t9 |
| 24 | o3f5N+4DczgqzloV7U6/DFeMZ23nwBD9IgrUf6KyGoC/sJ4twTjpxSECekiOHgyn |
| 25 | /M7SwrBTJYJTsMXzqYBPVjk7f17+7LAwNqBNzQx6HsGV8sZX4kDlcMAL53obLV1z |
| 26 | kkJHhP3suaWCUl5uarZ9r5vc7DC2j6vvtghFsDvRWqqram3fCEOJOQmoAVlChbtq |
| 27 | SwIDAQABo3IwcDAdBgNVHQ4EFgQUEicpezwWkZY3JMEwx1D0eraiFCEwHwYDVR0j |
| 28 | BBgwFoAUEicpezwWkZY3JMEwx1D0eraiFCEwDwYDVR0TAQH/BAUwAwEB/zAdBgNV |
| 29 | HREEFjAUghJhbGNoZW15LXNzbC1hLnRlc3QwDQYJKoZIhvcNAQELBQADggEBAMRj |
| 30 | u6+KYAYwX/cuJruXaX7Pa9kX4Es+637dDPWBP9lLedVOGEpOujiDn2JUuNZNl6vo |
| 31 | eqaJrjQuJkSgVVYikz3dGWTEvqlDIJ5nbRIo8etW1d5eoZeWcd95t2VXNRlfULBS |
| 32 | eNOFYOI6iaPW2CQXFvNAgER/Azj8Y4ilky504m0bqkZWTl/2f+x1KaiBqkxqiOdX |
| 33 | n5gPpu+jujVOyJni7KJo4+iIdWtQI1xAazXyOFefTMkPGLkXeQ/+tXrmY9NoVvNO |
| 34 | JBEe8o+wi/LxH/K9/It1rCfWckb5zHU57li4ZxetlwYssVW4Of25sMp9c4JNF3gJ |
| 35 | mkhcNRA0cRkbnom+kn8= |
| 36 | -----END CERTIFICATE-----`; |
| 37 | |
| 38 | const CERT_B = `-----BEGIN CERTIFICATE----- |
| 39 | MIIDOjCCAiKgAwIBAgIUW1TNbunaoG4/NzJZSThMKRTZuw4wDQYJKoZIhvcNAQEL |
| 40 | BQAwHTEbMBkGA1UEAwwSYWxjaGVteS1zc2wtYi50ZXN0MB4XDTI2MDgyNDE1NDM0 |
| 41 | NFoXDTM2MDgyMTE1NDM0NFowHTEbMBkGA1UEAwwSYWxjaGVteS1zc2wtYi50ZXN0 |
| 42 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA77IIHhQKx9IOuqN9lUwn |
| 43 | oqPG+LeafxTE9cKaQsO21FgjlsSvHlZqGdtdojevDYucWYgWwtOpj5FASpMGW5Kg |
| 44 | 2PcZk8dgpXT6byYIicDA8jJVx93sFBxn2BhYvvCfWRbqPSo3SoF3DCS6AQiZX8zl |
| 45 | u+Cs63Tfo2OCbCqlPylm2DGhNfs1KI22lLrU3ay0zHjeUtiK4+CQVij8He+e48mH |
| 46 | MwwWtWXKP/eikQlnV4c6OYA6rLLXBvKy3g1zavBAEY5GLECP/xt1bj2qgTUge8L4 |
| 47 | Xr45pBj3l6Ku1thDefS9mXcHt456VeEkWir0O7cnBN903BbzhNV7vTHVfS5I8ZTg |
| 48 | jQIDAQABo3IwcDAdBgNVHQ4EFgQU07mM+3JilvETyfGSF5I6Al+B8gIwHwYDVR0j |
| 49 | BBgwFoAU07mM+3JilvETyfGSF5I6Al+B8gIwDwYDVR0TAQH/BAUwAwEB/zAdBgNV |
| 50 | HREEFjAUghJhbGNoZW15LXNzbC1iLnRlc3QwDQYJKoZIhvcNAQELBQADggEBAOFT |
| 51 | KwWAxvo2/g+ErYxKT7Tl3ObBmUGC93T2KX38bw2jq7+7dgTJnz2kN6V5PRaq1iXj |
| 52 | lmCF++dJJpCPdSiBhYNkoUIk7st0n9Mz/3oJBzass3wFY0dHw7r895rTfjkRF+F5 |
| 53 | Fw9dTXzy0a0Gni4I6jEWflEVXAFb0q6RJ6zHdGiriJsXvXKC8eVBJqRCy+NbufpL |
| 54 | efSfgplQfXSteOlOnRP9iWsK3AH1TVw6RwLaT3iVWSbWjICf3f+8xJ65j/vNjbnY |
| 55 | 2F2+hBmKEXfRv5liZIhV1Q9gxbpjvzw2OzaoQxkZYNbaU96yy5TsG1jGd0wqX/vT |
| 56 | MbTsvjb7gbJeTqYGQeA= |
| 57 | -----END CERTIFICATE-----`; |
| 58 | |
| 59 | const waitUntilGone = (name: string) => |
| 60 | certificatemanager.getProjectsLocationsTrustConfigs({ name }).pipe( |
| 61 | Effect.as("found" as const), |
| 62 | Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)), |
| 63 | Effect.repeat({ |
| 64 | schedule: Schedule.spaced("1 second"), |
| 65 | until: (status) => status === "gone", |
| 66 | times: 10, |
| 67 | }), |
| 68 | ); |
| 69 | |
| 70 | const pemOf = (value: string | undefined) => (value ?? "").replace(/\s+/g, ""); |
| 71 | |
| 72 | test.provider( |
| 73 | "create, update, and delete a trust config", |
| 74 | (stack) => |
| 75 | Effect.gen(function* () { |
| 76 | yield* stack.destroy(); |
| 77 | |
| 78 | const created = yield* stack.deploy( |
| 79 | Effect.gen(function* () { |
| 80 | return yield* GCP.CertificateManager.TrustConfig("ClientMtls", { |
| 81 | location: "global", |
| 82 | description: "mtls a", |
| 83 | labels: { env: "test" }, |
| 84 | trustStores: [ |
| 85 | { |
| 86 | trustAnchors: [{ pemCertificate: CERT_A }], |
| 87 | }, |
| 88 | ], |
| 89 | }); |
| 90 | }), |
| 91 | ); |
| 92 | |
| 93 | expect(created.name).toContain("/trustConfigs/"); |
| 94 | expect(created.trustConfigId).toEqual(expect.any(String)); |
| 95 | expect(created.location).toEqual("global"); |
| 96 | expect(created.description).toEqual("mtls a"); |
| 97 | expect(created.labels).toMatchObject({ env: "test" }); |
| 98 | expect(created.trustStores).toHaveLength(1); |
| 99 | expect( |
| 100 | pemOf(created.trustStores[0]?.trustAnchors?.[0]?.pemCertificate), |
| 101 | ).toEqual(pemOf(CERT_A)); |
| 102 | |
| 103 | const fetched = |
| 104 | yield* certificatemanager.getProjectsLocationsTrustConfigs({ |
| 105 | name: created.name, |
| 106 | }); |
| 107 | expect(fetched.name).toEqual(created.name); |
| 108 | expect(fetched.description).toEqual("mtls a"); |
| 109 | expect(fetched.labels?.env).toEqual("test"); |
| 110 | expect(fetched.trustStores).toHaveLength(1); |
| 111 | expect( |
| 112 | pemOf(fetched.trustStores?.[0]?.trustAnchors?.[0]?.pemCertificate), |
| 113 | ).toEqual(pemOf(CERT_A)); |
| 114 | expect( |
| 115 | Object.keys(fetched.labels ?? {}).some((key) => |
| 116 | key.startsWith("alchemy-"), |
| 117 | ), |
| 118 | ).toEqual(true); |
| 119 | |
| 120 | const updated = yield* stack.deploy( |
| 121 | Effect.gen(function* () { |
| 122 | return yield* GCP.CertificateManager.TrustConfig("ClientMtls", { |
| 123 | trustConfigId: created.trustConfigId, |
| 124 | location: "global", |
| 125 | description: "mtls b", |
| 126 | labels: { env: "prod", role: "mtls" }, |
| 127 | trustStores: [ |
| 128 | { |
| 129 | trustAnchors: [{ pemCertificate: CERT_A }], |
| 130 | }, |
| 131 | ], |
| 132 | allowlistedCertificates: [{ pemCertificate: CERT_B }], |
| 133 | }); |
| 134 | }), |
| 135 | ); |
| 136 | |
| 137 | expect(updated.name).toEqual(created.name); |
| 138 | expect(updated.description).toEqual("mtls b"); |
| 139 | expect(updated.labels).toMatchObject({ env: "prod", role: "mtls" }); |
| 140 | expect(updated.allowlistedCertificates).toHaveLength(1); |
| 141 | expect(pemOf(updated.allowlistedCertificates[0]?.pemCertificate)).toEqual( |
| 142 | pemOf(CERT_B), |
| 143 | ); |
| 144 | |
| 145 | const refetched = |
| 146 | yield* certificatemanager.getProjectsLocationsTrustConfigs({ |
| 147 | name: created.name, |
| 148 | }); |
| 149 | expect(refetched.description).toEqual("mtls b"); |
| 150 | expect(refetched.labels?.env).toEqual("prod"); |
| 151 | expect(refetched.labels?.role).toEqual("mtls"); |
| 152 | expect( |
| 153 | pemOf(refetched.allowlistedCertificates?.[0]?.pemCertificate), |
| 154 | ).toEqual(pemOf(CERT_B)); |
| 155 | expect( |
| 156 | pemOf(refetched.trustStores?.[0]?.trustAnchors?.[0]?.pemCertificate), |
| 157 | ).toEqual(pemOf(CERT_A)); |
| 158 | |
| 159 | yield* stack.destroy(); |
| 160 | |
| 161 | const gone = yield* waitUntilGone(created.name); |
| 162 | expect(gone).toEqual("gone"); |
| 163 | }).pipe(logLevel), |
| 164 | { |
| 165 | tags: ["provider:gcp", "provider:gcp:certificatemanager", "live"], |
| 166 | timeout: 90_000, |
| 167 | }, |
| 168 | ); |