Compare commits

...

288 Commits

Author SHA1 Message Date
github-actions[bot] 9bf6a8369f
Merge pull request #1891 from smallstep/dependabot/go_modules/google.golang.org/protobuf-1.34.2
Bump google.golang.org/protobuf from 1.34.1 to 1.34.2
1 week ago
dependabot[bot] 43bdd61803
Bump google.golang.org/protobuf from 1.34.1 to 1.34.2
Bumps google.golang.org/protobuf from 1.34.1 to 1.34.2.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
1 week ago
github-actions[bot] 1563c264bd
Merge pull request #1890 from smallstep/dependabot/go_modules/google.golang.org/api-0.184.0
Bump google.golang.org/api from 0.183.0 to 0.184.0
1 week ago
github-actions[bot] 14230d8696
Merge pull request #1889 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.47.1
Bump go.step.sm/crypto from 0.47.0 to 0.47.1
1 week ago
dependabot[bot] 958c344f18
Bump google.golang.org/api from 0.183.0 to 0.184.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.183.0 to 0.184.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.183.0...v0.184.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
1 week ago
dependabot[bot] 8a7892463d
Bump go.step.sm/crypto from 0.47.0 to 0.47.1
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.47.0 to 0.47.1.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.47.0...v0.47.1)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
1 week ago
Max 890e81cb66
[actions] dependabot to common workflow | move to new release action (#1887) 2 weeks ago
github-actions[bot] 2d4bc95460
Merge pull request #1883 from smallstep/dependabot/go_modules/github.com/Azure/azure-sdk-for-go/sdk/azidentity-1.6.0
Bump github.com/Azure/azure-sdk-for-go/sdk/azidentity from 1.5.2 to 1.6.0
2 weeks ago
Max 93ca1e21c1
Merge branch 'master' into dependabot/go_modules/github.com/Azure/azure-sdk-for-go/sdk/azidentity-1.6.0 2 weeks ago
Max d6973c979e
Set date for 0.26.2 release in changelog (#1886) 2 weeks ago
Max d4b2916650
Changelog update for 0.26.2 (#1885) 2 weeks ago
Mariano Cano f9e5971978
Merge pull request #1884 from smallstep/mariano/linkedca
Upgrade linkedca
2 weeks ago
Mariano Cano c8e65abfb1
Fix linter warnings 2 weeks ago
Mariano Cano b4616ee8f1
Upgrade linkedca
This commit upgrades go.step.sm/linkedca with dependencies required by
smallstep/certificates#1802. It also fixes some linter warnings.
2 weeks ago
Mariano Cano 634ece4489
Merge pull request #1802 from jdoupe/AuthParams
add AuthParams to OIDC struct
2 weeks ago
Mariano Cano a017c0e3fb
Merge branch 'master' into AuthParams 2 weeks ago
dependabot[bot] 7bc9d15849
Bump github.com/Azure/azure-sdk-for-go/sdk/azidentity
Bumps [github.com/Azure/azure-sdk-for-go/sdk/azidentity](https://github.com/Azure/azure-sdk-for-go) from 1.5.2 to 1.6.0.
- [Release notes](https://github.com/Azure/azure-sdk-for-go/releases)
- [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md)
- [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/internal/v1.5.2...sdk/azcore/v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azidentity
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2 weeks ago
github-actions[bot] 8b36f7bc11
Merge pull request #1878 from smallstep/dependabot/go_modules/google.golang.org/api-0.183.0
Bump google.golang.org/api from 0.182.0 to 0.183.0
2 weeks ago
Max 30b2cd1e5c
Merge branch 'master' into dependabot/go_modules/google.golang.org/api-0.183.0 2 weeks ago
github-actions[bot] a0b9360725
Merge pull request #1879 from smallstep/dependabot/go_modules/golang.org/x/crypto-0.24.0
Bump golang.org/x/crypto from 0.23.0 to 0.24.0
2 weeks ago
Max d5171be998
Merge branch 'master' into dependabot/go_modules/golang.org/x/crypto-0.24.0 2 weeks ago
github-actions[bot] 6e12cfa46c
Merge pull request #1880 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.47.0
Bump go.step.sm/crypto from 0.46.0 to 0.47.0
2 weeks ago
Max d1de1ad8df
Merge branch 'master' into dependabot/go_modules/google.golang.org/api-0.183.0 2 weeks ago
Max 0ce8fb6db5
Merge branch 'master' into dependabot/go_modules/golang.org/x/crypto-0.24.0 2 weeks ago
Max 3b9631b883
Merge branch 'master' into dependabot/go_modules/go.step.sm/crypto-0.47.0 2 weeks ago
Max 474f5d28f4
Update hardcoded AWS certs (#1881) 2 weeks ago
dependabot[bot] 7ab8391070
Bump go.step.sm/crypto from 0.46.0 to 0.47.0
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.46.0 to 0.47.0.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.46.0...v0.47.0)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 weeks ago
dependabot[bot] 23f120e9db
Bump golang.org/x/crypto from 0.23.0 to 0.24.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.23.0 to 0.24.0.
- [Commits](https://github.com/golang/crypto/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 weeks ago
dependabot[bot] e3444c0736
Bump google.golang.org/api from 0.182.0 to 0.183.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.182.0 to 0.183.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.182.0...v0.183.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 weeks ago
github-actions[bot] 669d992d59
Merge pull request #1870 from smallstep/dependabot/go_modules/google.golang.org/api-0.182.0
Bump google.golang.org/api from 0.181.0 to 0.182.0
3 weeks ago
github-actions[bot] 68c5238fe7
Merge pull request #1869 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api/auth/approle-0.7.0
Bump github.com/hashicorp/vault/api/auth/approle from 0.6.0 to 0.7.0
3 weeks ago
github-actions[bot] 4884379710
Merge pull request #1868 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.46.0
Bump go.step.sm/crypto from 0.45.1 to 0.46.0
3 weeks ago
dependabot[bot] 437154d70e
Bump google.golang.org/api from 0.181.0 to 0.182.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.181.0 to 0.182.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.181.0...v0.182.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 weeks ago
dependabot[bot] 2a9bbff8ce
Bump github.com/hashicorp/vault/api/auth/approle from 0.6.0 to 0.7.0
Bumps [github.com/hashicorp/vault/api/auth/approle](https://github.com/hashicorp/vault) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG-v0.md)
- [Commits](https://github.com/hashicorp/vault/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api/auth/approle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 weeks ago
dependabot[bot] 4d7ca9d672
Bump go.step.sm/crypto from 0.45.1 to 0.46.0
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.45.1 to 0.46.0.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.45.1...v0.46.0)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 weeks ago
github-actions[bot] 587d0d5a27
Merge pull request #1858 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.17.0
Bump cloud.google.com/go/security from 1.16.1 to 1.17.0
4 weeks ago
dependabot[bot] 34fde59927
Bump cloud.google.com/go/security from 1.16.1 to 1.17.0
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.16.1 to 1.17.0.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/documentai/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/video/v1.16.1...kms/v1.17.0)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/security
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 weeks ago
github-actions[bot] fe8c3d3ee8
Merge pull request #1859 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api/auth/kubernetes-0.7.0
Bump github.com/hashicorp/vault/api/auth/kubernetes from 0.6.0 to 0.7.0
4 weeks ago
dependabot[bot] 013c2f2f04
Bump github.com/hashicorp/vault/api/auth/kubernetes from 0.6.0 to 0.7.0
Bumps [github.com/hashicorp/vault/api/auth/kubernetes](https://github.com/hashicorp/vault) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG-v0.md)
- [Commits](https://github.com/hashicorp/vault/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api/auth/kubernetes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 weeks ago
github-actions[bot] 4208b0a63f
Merge pull request #1860 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api-1.14.0
Bump github.com/hashicorp/vault/api from 1.13.0 to 1.14.0
4 weeks ago
github-actions[bot] 6de7aa97dc
Merge pull request #1861 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.45.1
Bump go.step.sm/crypto from 0.45.0 to 0.45.1
4 weeks ago
dependabot[bot] f3e4f0ae64
Bump go.step.sm/crypto from 0.45.0 to 0.45.1
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.45.0 to 0.45.1.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.45.0...v0.45.1)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
4 weeks ago
dependabot[bot] 2b8f3e7080
Bump github.com/hashicorp/vault/api from 1.13.0 to 1.14.0
Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/vault/compare/v1.13.0...v1.14.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 weeks ago
Mariano Cano 47b5048d82
Merge pull request #1850 from smallstep/mariano/signer
Add GetX509Signer method
1 month ago
github-actions[bot] 7d6eea0ff0
Merge pull request #1853 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.64.0
Bump google.golang.org/grpc from 1.63.2 to 1.64.0
1 month ago
max furman 99ce13a4ea
Fix linter warnings 1 month ago
dependabot[bot] 5cdfc2c972
Bump google.golang.org/grpc from 1.63.2 to 1.64.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.63.2 to 1.64.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.63.2...v1.64.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
github-actions[bot] 980687bc0e
Merge pull request #1854 from smallstep/dependabot/go_modules/google.golang.org/api-0.181.0
Bump google.golang.org/api from 0.180.0 to 0.181.0
1 month ago
dependabot[bot] 8121a05c55
Bump google.golang.org/api from 0.180.0 to 0.181.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.180.0 to 0.181.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.180.0...v0.181.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
Mariano Cano ad0ac55b41
Merge pull request #1844 from smallstep/mariano/account-provisioner
Verify provisioner with id if available
1 month ago
Mariano Cano 192e90eea7
Merge branch 'master' into mariano/account-provisioner 1 month ago
Mariano Cano 812ffd3c40
Reverse assert statements 1 month ago
Mariano Cano d0548f9ec9
Use %q instead of '%s'
Co-authored-by: Herman Slatman <hslatman@users.noreply.github.com>
1 month ago
Mariano Cano 14959dbb2e
Merge pull request #1849 from smallstep/mariano/log-errors
Log errors using slog.Logger
1 month ago
Mariano Cano c0b7c33a58
Use a function as the error logger
This commit addresses comment in the code review. Now, instead of
injecting an slog.Logger we can inject any method that can use a more
flexible implementation.
1 month ago
Mariano Cano 9e8087fbb1
Add GetX509Signer method
This commit adds a method to the Authority type that returns the signer
used to sign X509 certificates.
1 month ago
Mariano Cano 8673818980
Split provisioner check in two cases 1 month ago
Mariano Cano f3f484cee2
Log errors using slog.Logger
This commit allows logging errors in a slog.Logger injected in the
context. This type of logger is not currently used directly in step-ca,
but this will change in the future.
1 month ago
Mariano Cano fdb0cf03f6
Merge pull request #1848 from smallstep/mariano/intermediates
Add methods to get the intermediate certificates
1 month ago
Mariano Cano d4862a2520
Add methods to get the intermediate certificates
This commit adds new methods to get the intermediate certificates.
1 month ago
Mariano Cano e08b27708b
Merge pull request #1847 from smallstep/mariano/x5c-insecure
Upgrade go.step.sm/crypto to v0.45.0
1 month ago
Mariano Cano b6afed3be7
Upgrade go.step.sm/crypto to v0.45.0
This commit upgrades go.step.sm/crypto to a version that fixes
`jose.ParseX5cInsecure`. It should require a certificate with ClientAuth
instead of the default ServerAuth.
1 month ago
github-actions[bot] 9355923799
Merge pull request #1839 from smallstep/dependabot/go_modules/google.golang.org/api-0.180.0
Bump google.golang.org/api from 0.177.0 to 0.180.0
1 month ago
dependabot[bot] a8e9a18bcc
Bump google.golang.org/api from 0.177.0 to 0.180.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.177.0 to 0.180.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.177.0...v0.180.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
github-actions[bot] 803d3d3981
Merge pull request #1840 from smallstep/dependabot/go_modules/google.golang.org/protobuf-1.34.1
Bump google.golang.org/protobuf from 1.34.0 to 1.34.1
1 month ago
github-actions[bot] e0e7ae6c89
Merge pull request #1841 from smallstep/dependabot/go_modules/golang.org/x/net-0.25.0
Bump golang.org/x/net from 0.24.0 to 0.25.0
1 month ago
github-actions[bot] 72a8bb3d39
Merge pull request #1842 from smallstep/dependabot/go_modules/github.com/prometheus/client_golang-1.19.1
Bump github.com/prometheus/client_golang from 1.19.0 to 1.19.1
1 month ago
Mariano Cano 5fa5a63de8
Verify provisioner with id if available
This commit allows verifying the account provisioner with the request
provisioner using the ID instead of the name. When the provisioner are
stored in the DB, the provisioner ID is not mutable but the name is.
1 month ago
dependabot[bot] 9cbdc73809
Bump github.com/prometheus/client_golang from 1.19.0 to 1.19.1
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.19.0 to 1.19.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.19.0...v1.19.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
dependabot[bot] 42341c7a39
Bump golang.org/x/net from 0.24.0 to 0.25.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/golang/net/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
dependabot[bot] 0dff5c4b84
Bump google.golang.org/protobuf from 1.34.0 to 1.34.1
Bumps google.golang.org/protobuf from 1.34.0 to 1.34.1.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
github-actions[bot] e3ba702811
Merge pull request #1827 from smallstep/dependabot/go_modules/golang.org/x/crypto-0.23.0
Bump golang.org/x/crypto from 0.22.0 to 0.23.0
2 months ago
github-actions[bot] fe29ccaee3
Merge pull request #1828 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.33.0
Bump github.com/newrelic/go-agent/v3 from 3.32.0 to 3.33.0
2 months ago
github-actions[bot] 8cf5e3c2c6
Merge pull request #1829 from smallstep/dependabot/go_modules/cloud.google.com/go/longrunning-0.5.7
Bump cloud.google.com/go/longrunning from 0.5.6 to 0.5.7
2 months ago
dependabot[bot] 928d446e93
Bump golang.org/x/crypto from 0.22.0 to 0.23.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.22.0 to 0.23.0.
- [Commits](https://github.com/golang/crypto/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
dependabot[bot] e11833ef7a
Bump cloud.google.com/go/longrunning from 0.5.6 to 0.5.7
Bumps [cloud.google.com/go/longrunning](https://github.com/googleapis/google-cloud-go) from 0.5.6 to 0.5.7.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/longrunning/v0.5.6...longrunning/v0.5.7)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/longrunning
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
github-actions[bot] 591b9f743d
Merge pull request #1826 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.16.1
Bump cloud.google.com/go/security from 1.16.0 to 1.16.1
2 months ago
Mariano Cano a2f2332848
Merge pull request #1831 from smallstep/mariano/err-not-found
Use always acme.IsErrNotFound
2 months ago
Mariano Cano b1e31b1726
Use always acme.IsErrNotFound
This commit replaces the comparisons with acme.ErrNotFound and always
uses acme.IsErrNotFound.
2 months ago
Mariano Cano cca6f6d01b
Merge pull request #1830 from smallstep/mariano/provisioner-id
Add provisioner id to acme accounts
2 months ago
Mariano Cano d037ed6ff2
Add provisioner id to acme accounts
This commit adds a new field that allows to have a reference to
a provisioner id in the acme account.
2 months ago
dependabot[bot] 9b2566556e
Bump github.com/newrelic/go-agent/v3 from 3.32.0 to 3.33.0
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.32.0 to 3.33.0.
- [Release notes](https://github.com/newrelic/go-agent/releases)
- [Changelog](https://github.com/newrelic/go-agent/blob/master/CHANGELOG.md)
- [Commits](https://github.com/newrelic/go-agent/compare/v3.32.0...v3.33.0)

---
updated-dependencies:
- dependency-name: github.com/newrelic/go-agent/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
dependabot[bot] 8933a2e514
Bump cloud.google.com/go/security from 1.16.0 to 1.16.1
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.16.0 to 1.16.1.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/documentai/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/video/v1.16.0...video/v1.16.1)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/security
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
github-actions[bot] 2c71543d33
Merge pull request #1817 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.44.8
Bump go.step.sm/crypto from 0.44.6 to 0.44.8
2 months ago
Herman Slatman 949e2fdb1c
Fix test error expectation in `TestAuthorityNew` 2 months ago
dependabot[bot] 281efbb949
Bump go.step.sm/crypto from 0.44.6 to 0.44.8
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.44.6 to 0.44.8.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.44.6...v0.44.8)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
github-actions[bot] 14b1211e7a
Merge pull request #1815 from smallstep/dependabot/go_modules/github.com/urfave/cli-1.22.15
Bump github.com/urfave/cli from 1.22.14 to 1.22.15
2 months ago
github-actions[bot] 0b894a0d4a
Merge pull request #1816 from smallstep/dependabot/go_modules/google.golang.org/api-0.176.1
Bump google.golang.org/api from 0.176.0 to 0.176.1
2 months ago
Mariano Cano 20e315bac0
Merge pull request #1819 from smallstep/mariano/not-found
Make IsErrNotFound more flexible
2 months ago
Mariano Cano 296ac4e207
Make ISErrNotFound more flexible
This commit allows to use the standard error sql.ErrNoRows for not found
errors.
2 months ago
github-actions[bot] 28a87bbaa6
Merge pull request #1818 from smallstep/dependabot/github_actions/dependabot/fetch-metadata-2.1.0
Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0
2 months ago
dependabot[bot] bf03d56ae4
Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: dependabot/fetch-metadata
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
dependabot[bot] 6715c653ca
Bump google.golang.org/api from 0.176.0 to 0.176.1
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.176.0 to 0.176.1.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.176.0...v0.176.1)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
dependabot[bot] 798e190621
Bump github.com/urfave/cli from 1.22.14 to 1.22.15
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.14 to 1.22.15.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.22.14...v1.22.15)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
findnature 5072d7a58f
chore: fix function names in comment (#1813)
Signed-off-by: findnature <cricis@aliyun.com>
2 months ago
Max 9cbab5a5b3
Add changelog for 0.26.1 (#1812) 2 months ago
Jeremy Doupe 03c3cf5790 fixed Scopes and AuthParams assignment 2 months ago
Herman Slatman d6bf551f87
Merge pull request #1803 from smallstep/herman/fix-scep-vault-ra
Fix CA startup with Vault RA configuration
2 months ago
github-actions[bot] f4d506f659
Merge pull request #1811 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api-1.13.0
Bump github.com/hashicorp/vault/api from 1.12.2 to 1.13.0
2 months ago
Herman Slatman 1e5e267b2b
Remove leftover debug print 2 months ago
max furman 760014c64b
go mod tidy 2 months ago
Herman Slatman 2561a7271e
Dedupe CA and SCEP client creation logic 2 months ago
dependabot[bot] 39653050dc
Bump github.com/hashicorp/vault/api from 1.12.2 to 1.13.0
Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.12.2 to 1.13.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/vault/compare/v1.12.2...v1.13.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
github-actions[bot] 65cfee56df
Merge pull request #1810 from smallstep/dependabot/go_modules/google.golang.org/api-0.176.0
Bump google.golang.org/api from 0.172.0 to 0.176.0
2 months ago
dependabot[bot] 8d4effcce8
Bump google.golang.org/api from 0.172.0 to 0.176.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.172.0 to 0.176.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.172.0...v0.176.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
github-actions[bot] 4a375592d3
Merge pull request #1809 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.44.6
Bump go.step.sm/crypto from 0.44.4 to 0.44.6
2 months ago
github-actions[bot] d7ed03124f
Merge pull request #1808 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.16.0
Bump cloud.google.com/go/security from 1.15.6 to 1.16.0
2 months ago
Herman Slatman 87202001a8
Rewrite SCEP integration tests to only use the HTTPS endpoint 2 months ago
dependabot[bot] 57a6b85388
Bump go.step.sm/crypto from 0.44.4 to 0.44.6
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.44.4 to 0.44.6.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.44.4...v0.44.6)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
dependabot[bot] 0ba61c58e8
Bump cloud.google.com/go/security from 1.15.6 to 1.16.0
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.15.6 to 1.16.0.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/documentai/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/kms/v1.15.6...asset/v1.16.0)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/security
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
Herman Slatman b0fabe1346
Add some SCEP integration tests 2 months ago
Herman Slatman 113a6dd8ab
Remove reporting the CA mode from startup logs 2 months ago
Herman Slatman 6bc0a86207
Fix CA startup with Vault RA configuration 2 months ago
Jeremy Doupe aa543a335a
add Scopes to OIDC struct 2 months ago
Jeremy Doupe 4879376138
add AuthParams and Scopes to linkedca OIDC structures 2 months ago
Jeremy Doupe 2fcf34066b
add AuthParams to OIDC struct 2 months ago
Herman Slatman 07279dd248
Merge pull request #1801 from smallstep/herman/upgrade-crypto-v0.44.4
Upgrade `go.step.sm/crypto` to `v0.44.4`
2 months ago
Herman Slatman 4c6b0b318e
Upgrade `go.step.sm/crypto` to `v0.44.4` 2 months ago
Herman Slatman f1a2c68f43
Merge pull request #1798 from smallstep/herman/fix-instrumented-key-manager
Add support for `kmsapi.Decrypter` to instrumented key manager
2 months ago
Mariano Cano 7df3ad05ed
Merge pull request #1797 from smallstep/mariano/init-scep
Allow custom SCEP key manager
3 months ago
Mariano Cano 4202d6673c
Remove debug statement 3 months ago
Herman Slatman d6bbe5b06b
Add support for `kmsapi.Decrypter` to instrumented key manager 3 months ago
Herman Slatman 721345eea6
Merge pull request #1793 from verytrap/master
chore: fix function names in comment
3 months ago
verytrap db92404342 chore: fix function names in comment
Signed-off-by: verytrap <wangqiuyue@outlook.com>
3 months ago
Mariano Cano 725a913f66
Allow custom SCEP key manager
This commit allows to inject a custom key manger for SCEP.
3 months ago
Herman Slatman 397877a7b6
Merge pull request #1795 from smallstep/herman/fix-scep-failinfo-oid
Prevent exposing any internal details in SCEP failure message
3 months ago
Herman Slatman b226b6eb4c
Prevent exposing any internal details in SCEP failure message
To be on the safe side, block errors from signing operations from
being returned to the client. We should revisit, and make it return
a more informative error, but with high assurance that no sensitive
information is added to the message.
3 months ago
Herman Slatman 02956ad0e3
Merge pull request #1794 from smallstep/herman/fix-scep-failinfo-oid
Fix the `id-scep-failInfoText` OID
3 months ago
Herman Slatman 037554e774
Fix the `id-scep-failInfoText` OID 3 months ago
github-actions[bot] 1513152cb2
Merge pull request #1791 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.32.0
Bump github.com/newrelic/go-agent/v3 from 3.31.0 to 3.32.0
3 months ago
dependabot[bot] c9ba31ae61
Bump github.com/newrelic/go-agent/v3 from 3.31.0 to 3.32.0
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.31.0 to 3.32.0.
- [Release notes](https://github.com/newrelic/go-agent/releases)
- [Changelog](https://github.com/newrelic/go-agent/blob/master/CHANGELOG.md)
- [Commits](https://github.com/newrelic/go-agent/compare/v3.31.0...v3.32.0)

---
updated-dependencies:
- dependency-name: github.com/newrelic/go-agent/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
github-actions[bot] 1f69ff84a1
Merge pull request #1792 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.63.2
Bump google.golang.org/grpc from 1.62.1 to 1.63.2
3 months ago
dependabot[bot] a76f07143b
Bump google.golang.org/grpc from 1.62.1 to 1.63.2
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.62.1 to 1.63.2.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.62.1...v1.63.2)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
github-actions[bot] 08ef9fe245
Merge pull request #1789 from smallstep/dependabot/go_modules/golang.org/x/net-0.24.0
Bump golang.org/x/net from 0.22.0 to 0.24.0
3 months ago
dependabot[bot] 57d628513b
Bump golang.org/x/net from 0.22.0 to 0.24.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.22.0 to 0.24.0.
- [Commits](https://github.com/golang/net/compare/v0.22.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
github-actions[bot] d5758ba3a3
Merge pull request #1784 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.31.0
Bump github.com/newrelic/go-agent/v3 from 3.30.0 to 3.31.0
3 months ago
github-actions[bot] 166c4968cf
Merge pull request #1785 from smallstep/dependabot/go_modules/google.golang.org/api-0.172.0
Bump google.golang.org/api from 0.171.0 to 0.172.0
3 months ago
Carl Tashian 1be0932a0f
Merge pull request #1786 from smallstep/carl/winget-fix
Fix winget release notes URL
3 months ago
Carl Tashian f04a5e39c4
Fix winget release URL 3 months ago
dependabot[bot] d1523c93bc
Bump google.golang.org/api from 0.171.0 to 0.172.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.171.0 to 0.172.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.171.0...v0.172.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
dependabot[bot] 44c48a7494
Bump github.com/newrelic/go-agent/v3 from 3.30.0 to 3.31.0
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.30.0 to 3.31.0.
- [Release notes](https://github.com/newrelic/go-agent/releases)
- [Changelog](https://github.com/newrelic/go-agent/blob/master/CHANGELOG.md)
- [Commits](https://github.com/newrelic/go-agent/compare/v3.30.0...v3.31.0)

---
updated-dependencies:
- dependency-name: github.com/newrelic/go-agent/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
Carl Tashian 188e4e3ff3
Add version number to winget branch name (#1783) 3 months ago
Max 395a3eeb93
Update go.step.sm/crypto (#1781) 3 months ago
Herman Slatman 4772d7cc28
Merge pull request #1780 from smallstep/herman/update-changelog-20240328
Update changelog for `v0.26.0` release
3 months ago
Herman Slatman 854288a0cb
Update changelog for `v0.26.0` release 3 months ago
github-actions[bot] 4016b69b28
Merge pull request #1776 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api-1.12.2
Bump github.com/hashicorp/vault/api from 1.12.1 to 1.12.2
3 months ago
github-actions[bot] b5b723e835
Merge pull request #1775 from smallstep/dependabot/go_modules/google.golang.org/api-0.171.0
Bump google.golang.org/api from 0.169.0 to 0.171.0
3 months ago
github-actions[bot] 0a6e79a745
Merge pull request #1778 from smallstep/dependabot/github_actions/dependabot/fetch-metadata-2.0.0
Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0
3 months ago
dependabot[bot] 9d86361ae3
Bump github.com/hashicorp/vault/api from 1.12.1 to 1.12.2
Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.12.1 to 1.12.2.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/vault/compare/v1.12.1...v1.12.2)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
github-actions[bot] 7e053437b1
Merge pull request #1774 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.44.1
Bump go.step.sm/crypto from 0.43.1 to 0.44.1
3 months ago
dependabot[bot] 014b4ef2c0
Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.6.0 to 2.0.0.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.6.0...v2.0.0)

---
updated-dependencies:
- dependency-name: dependabot/fetch-metadata
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
dependabot[bot] 21734f7742
Bump google.golang.org/api from 0.169.0 to 0.171.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.169.0 to 0.171.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.169.0...v0.171.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
dependabot[bot] 927cd97bd5
Bump go.step.sm/crypto from 0.43.1 to 0.44.1
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.43.1 to 0.44.1.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.43.1...v0.44.1)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
Herman Slatman 2650944d65
Merge pull request #1773 from smallstep/herman/cosign-2.x
Use `--yes` to acknowledge user prompts for `cosign` signing
3 months ago
Herman Slatman 7888d868ba
Use `--yes` to acknowledge user prompts for `cosign` signing 3 months ago
Joe Doss 56c4f3bdd5
Merge pull request #1772 from smallstep/jdoss/Enable_tpmkms
Enable tpmkms
3 months ago
Joe Doss 14c9de2570
Enable tpmkms. 3 months ago
github-actions[bot] 44f44e34e4
Merge pull request #1767 from smallstep/dependabot/go_modules/cloud.google.com/go/longrunning-0.5.6
Bump cloud.google.com/go/longrunning from 0.5.5 to 0.5.6
3 months ago
github-actions[bot] 73c827c7fa
Merge pull request #1768 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api-1.12.1
Bump github.com/hashicorp/vault/api from 1.12.0 to 1.12.1
3 months ago
dependabot[bot] 9874214669
Bump cloud.google.com/go/longrunning from 0.5.5 to 0.5.6
Bumps [cloud.google.com/go/longrunning](https://github.com/googleapis/google-cloud-go) from 0.5.5 to 0.5.6.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/longrunning/v0.5.5...longrunning/v0.5.6)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/longrunning
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
github-actions[bot] c5a13d86ba
Merge pull request #1766 from smallstep/dependabot/go_modules/golang.org/x/net-0.22.0
Bump golang.org/x/net from 0.21.0 to 0.22.0
3 months ago
github-actions[bot] 70085fcfeb
Merge pull request #1769 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.15.6
Bump cloud.google.com/go/security from 1.15.5 to 1.15.6
3 months ago
dependabot[bot] 1a768ad522
Bump cloud.google.com/go/security from 1.15.5 to 1.15.6
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.15.5 to 1.15.6.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/kms/v1.15.5...kms/v1.15.6)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/security
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
dependabot[bot] 4dfade10b8
Bump github.com/hashicorp/vault/api from 1.12.0 to 1.12.1
Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.12.0 to 1.12.1.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/vault/compare/v1.12.0...v1.12.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
dependabot[bot] 9a75f93250
Bump golang.org/x/net from 0.21.0 to 0.22.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.22.0.
- [Commits](https://github.com/golang/net/compare/v0.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
Mariano Cano fe364d5081
Merge pull request #1764 from smallstep/mariano/ra-lifetime
Let the CA determine the RA lifetime
4 months ago
Mariano Cano 0ac9023590
Fix typo in error message and comment 4 months ago
Mariano Cano 10f6a901ec
Let the CA determine the RA lifetime
When the RA mode with StepCAS is used, let the CA decide which lifetime
the RA should get instead of requiring always 24h.

This commit also fixes linter warnings.

Related to #1094
4 months ago
Herman Slatman ef1631b00d
Merge pull request #1762 from smallstep/herman/upgrade-pgx-v4.18.3
Upgrade `pgx` to `v4.18.3`
4 months ago
Herman Slatman 6204a1441e
Upgrade `pgx` to `v4.18.3` 4 months ago
github-actions[bot] d03700b155
Merge pull request #1761 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.62.1
Bump google.golang.org/grpc from 1.62.0 to 1.62.1
4 months ago
github-actions[bot] 1362d4dea6
Merge pull request #1759 from smallstep/dependabot/go_modules/go.step.sm/cli-utils-0.9.0
Bump go.step.sm/cli-utils from 0.8.0 to 0.9.0
4 months ago
dependabot[bot] cc7ac97c51
Bump go.step.sm/cli-utils from 0.8.0 to 0.9.0
Bumps [go.step.sm/cli-utils](https://github.com/smallstep/cli-utils) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/smallstep/cli-utils/releases)
- [Commits](https://github.com/smallstep/cli-utils/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: go.step.sm/cli-utils
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
dependabot[bot] 22781b8460
Bump google.golang.org/grpc from 1.62.0 to 1.62.1
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.62.0 to 1.62.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.62.0...v1.62.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
github-actions[bot] 9033120401
Merge pull request #1758 from smallstep/dependabot/go_modules/google.golang.org/api-0.169.0
Bump google.golang.org/api from 0.167.0 to 0.169.0
4 months ago
github-actions[bot] 87506d738c
Merge pull request #1760 from smallstep/dependabot/go_modules/golang.org/x/crypto-0.21.0
Bump golang.org/x/crypto from 0.19.0 to 0.21.0
4 months ago
dependabot[bot] 4940556234
Bump golang.org/x/crypto from 0.19.0 to 0.21.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.19.0 to 0.21.0.
- [Commits](https://github.com/golang/crypto/compare/v0.19.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
dependabot[bot] 299102955b
Bump google.golang.org/api from 0.167.0 to 0.169.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.167.0 to 0.169.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.167.0...v0.169.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
Herman Slatman 5254702130
Merge pull request #1755 from smallstep/herman/export-requestid-middleware
Export `requestid` middleware
4 months ago
Herman Slatman b7a6b93717
Add package comment 4 months ago
github-actions[bot] 9b1dfcf5d0
Merge pull request #1756 from smallstep/dependabot/go_modules/github.com/go-jose/go-jose/v3-3.0.3
Bump github.com/go-jose/go-jose/v3 from 3.0.2 to 3.0.3
4 months ago
dependabot[bot] 9327859f55
Bump github.com/go-jose/go-jose/v3 from 3.0.2 to 3.0.3
Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/v3.0.3/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v3.0.2...v3.0.3)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
Carl Tashian 26b7761a9c
Merge pull request #1719 from smallstep/carl/fix-ra-install
Update RA installer step-ca package URL
4 months ago
Herman Slatman b8510dd5b2
Make the `requestid` an exported middleware 4 months ago
Herman Slatman 4cae19f94f
Merge pull request #1753 from smallstep/fix-peak-typo
Fix `peak` -> `peek`
4 months ago
Herman Slatman af76ebdc1d
Fix `peak` -> `peek` 4 months ago
github-actions[bot] 2e9c5bbc1a
Merge pull request #1747 from smallstep/dependabot/go_modules/github.com/go-chi/chi/v5-5.0.12
Bump github.com/go-chi/chi/v5 from 5.0.11 to 5.0.12
4 months ago
Herman Slatman a7a4b47ead
Merge pull request #1751 from smallstep/herman/handle-server-startup-errors
Handle CA server startup errors
4 months ago
Herman Slatman 01de10d447
Merge pull request #1752 from smallstep/herman/upgrade-protobuf-v1.33.0
Upgrade `google.golang.org/protobuf` to `v1.33.0`
4 months ago
Herman Slatman fa5117adaa
Upgrade `google.golang.org/protobuf` to `v1.33.0` 4 months ago
Herman Slatman bbb80cde16
Add startup error shutdown message to log 4 months ago
Herman Slatman f02d4546a9
Handle CA server startup errors 4 months ago
github-actions[bot] ec223c1cc2
Merge pull request #1748 from smallstep/dependabot/go_modules/github.com/stretchr/testify-1.9.0
Bump github.com/stretchr/testify from 1.8.4 to 1.9.0
4 months ago
github-actions[bot] b7e3e0be20
Merge pull request #1746 from smallstep/dependabot/go_modules/google.golang.org/api-0.167.0
Bump google.golang.org/api from 0.165.0 to 0.167.0
4 months ago
github-actions[bot] 022deaf610
Merge pull request #1749 from smallstep/dependabot/go_modules/github.com/prometheus/client_golang-1.19.0
Bump github.com/prometheus/client_golang from 1.18.0 to 1.19.0
4 months ago
dependabot[bot] 5853c73268
Bump github.com/prometheus/client_golang from 1.18.0 to 1.19.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.18.0 to 1.19.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.18.0...v1.19.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
dependabot[bot] cf0d6f8f5c
Bump github.com/stretchr/testify from 1.8.4 to 1.9.0
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.4 to 1.9.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.4...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
dependabot[bot] 3656b458ea
Bump github.com/go-chi/chi/v5 from 5.0.11 to 5.0.12
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.11 to 5.0.12.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.11...v5.0.12)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
dependabot[bot] 69c7ca9809
Bump google.golang.org/api from 0.165.0 to 0.167.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.165.0 to 0.167.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.165.0...v0.167.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
Herman Slatman 10aa48c74a
Merge pull request #1743 from smallstep/herman/improve-request-id
Improve end-to-end request ID propagation
4 months ago
Herman Slatman 2a47644d31
Fix linting issue 4 months ago
Herman Slatman d392c169fc
Improve functional coverage of request ID integration test 4 months ago
Herman Slatman 7fd524f70b
Default to generating request IDs using UUIDv4 format in CA 4 months ago
Herman Slatman 0898c6db97
Use UUIDv4 as automatically generated client request identifier 4 months ago
Carl Tashian 0d5c692b2c
Merge pull request #1744 from smallstep/carl/readme-updates
Updated README
4 months ago
Carl Tashian cd3e91b198
Updated README 4 months ago
Herman Slatman b9d6bfc1eb
Cleanup CA client tests by removing `smallstep/assert` 4 months ago
Herman Slatman 532b9df0a3
Improve CA client request ID handling 4 months ago
Herman Slatman 06696e6492
Move user ID handling to `userid` package 4 months ago
Herman Slatman 7e5f10927f
Decouple request ID middleware from logging middleware 4 months ago
Herman Slatman 535e2a96d5
Fix the e2e request ID test (again) 4 months ago
Herman Slatman b83b8aa079
Make random TCP address reservation more contained 4 months ago
Herman Slatman 2255857b3a
Fix `client` shadowing and e2e request ID test case 4 months ago
Herman Slatman 5c2572c443
Add support for user provider `X-Request-Id` header value 4 months ago
Herman Slatman cf8a50157f
Add a basic e2e test for `X-Request-Id` reflection 4 months ago
Panagiotis Siatras fb4cd6fe81
fix: Webhook-related instruments
* fix: also instrument webhooks that do not reach the wire
* fix: register the webhook instrumentation
4 months ago
Herman Slatman a58f5956e3
Add reflection of request ID in `X-Request-Id` response header 4 months ago
Herman Slatman c798735f7e
Merge pull request #1542 from smallstep/herman/webhook-request-id
Propagate request ID when webhook requests are made
4 months ago
Herman Slatman c1c2e73475
Add `X-Request-Id` to all requests made by our CA clients 4 months ago
Herman Slatman 4213a190d5
Use `X-Request-Id` as canonical request identifier (if available)
If `X-Request-Id` is available in an HTTP request made against the
CA server, it'll be used as the identifier for the request. This
slightly changes the existing behavior, which relied on the custom
`X-Smallstep-Id` header, but usage of that header is currently not
very widespread, and `X-Request-Id` is more generally known for
the use case `X-Smallstep-Id` is used for.

`X-Smallstep-Id` is currently still considered, but it'll only be
used if `X-Request-Id` is not set.
4 months ago
Herman Slatman 041b486c55
Remove usages of `Sign` without context 4 months ago
Herman Slatman c16a0b70ee
Remove `smallstep/assert` and `pkg/errors` from webhook tests 4 months ago
Herman Slatman 9689508709
Add tests for webhook request IDs 4 months ago
Herman Slatman 2a8b80a3e1
Merge branch 'master' into herman/webhook-request-id 4 months ago
github-actions[bot] 6ce502c50e
Merge pull request #1741 from smallstep/dependabot/go_modules/github.com/go-jose/go-jose/v3-3.0.2
Bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.2
4 months ago
github-actions[bot] 0d2aeff95c
Merge pull request #1739 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.62.0
Bump google.golang.org/grpc from 1.61.0 to 1.62.0
4 months ago
dependabot[bot] 5ee2e0274c
Bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.2
Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v3.0.1...v3.0.2)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
dependabot[bot] e4bbe8970e
Bump google.golang.org/grpc from 1.61.0 to 1.62.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.61.0 to 1.62.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.61.0...v1.62.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
github-actions[bot] 98a976b59e
Merge pull request #1740 from smallstep/dependabot/go_modules/github.com/fxamacker/cbor/v2-2.6.0
Bump github.com/fxamacker/cbor/v2 from 2.5.0 to 2.6.0
4 months ago
github-actions[bot] a583b59e1d
Merge pull request #1738 from smallstep/dependabot/go_modules/github.com/googleapis/gax-go/v2-2.12.2
Bump github.com/googleapis/gax-go/v2 from 2.12.0 to 2.12.2
4 months ago
dependabot[bot] 0b196b0b81
Bump github.com/fxamacker/cbor/v2 from 2.5.0 to 2.6.0
Bumps [github.com/fxamacker/cbor/v2](https://github.com/fxamacker/cbor) from 2.5.0 to 2.6.0.
- [Release notes](https://github.com/fxamacker/cbor/releases)
- [Commits](https://github.com/fxamacker/cbor/compare/v2.5.0...v2.6.0)

---
updated-dependencies:
- dependency-name: github.com/fxamacker/cbor/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
dependabot[bot] fa941dc967
Bump github.com/googleapis/gax-go/v2 from 2.12.0 to 2.12.2
Bumps [github.com/googleapis/gax-go/v2](https://github.com/googleapis/gax-go) from 2.12.0 to 2.12.2.
- [Release notes](https://github.com/googleapis/gax-go/releases)
- [Commits](https://github.com/googleapis/gax-go/compare/v2.12.0...v2.12.2)

---
updated-dependencies:
- dependency-name: github.com/googleapis/gax-go/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
Herman Slatman bb6aae0dfc
Merge pull request #1736 from patsevanton/master
Сorrection of spelling errors
4 months ago
Anton Patsev c2dfe595f1 Сorrection of spelling errors 4 months ago
Herman Slatman e968275a20
Merge pull request #1729 from patsevanton/master
Spelling errors and punctuation have been corrected
4 months ago
Herman Slatman 7e1b93b628
Update examples/README.md 4 months ago
github-actions[bot] dc577e2213
Merge pull request #1724 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.30.0
Bump github.com/newrelic/go-agent/v3 from 3.29.1 to 3.30.0
4 months ago
dependabot[bot] 3a2b426879
Bump github.com/newrelic/go-agent/v3 from 3.29.1 to 3.30.0
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.29.1 to 3.30.0.
- [Release notes](https://github.com/newrelic/go-agent/releases)
- [Changelog](https://github.com/newrelic/go-agent/blob/master/CHANGELOG.md)
- [Commits](https://github.com/newrelic/go-agent/compare/v3.29.1...v3.30.0)

---
updated-dependencies:
- dependency-name: github.com/newrelic/go-agent/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
github-actions[bot] f7554a0a43
Merge pull request #1725 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api/auth/kubernetes-0.6.0
Bump github.com/hashicorp/vault/api/auth/kubernetes from 0.5.0 to 0.6.0
4 months ago
github-actions[bot] 685e107bfe
Merge pull request #1726 from smallstep/dependabot/go_modules/google.golang.org/api-0.165.0
Bump google.golang.org/api from 0.160.0 to 0.165.0
4 months ago
Anton Patsev 0a074cb8ff Spelling errors and punctuation have been corrected 4 months ago
dependabot[bot] 8e1f53857b
Bump google.golang.org/api from 0.160.0 to 0.165.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.160.0 to 0.165.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.160.0...v0.165.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
github-actions[bot] e6491ca28b
Merge pull request #1727 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.43.1
Bump go.step.sm/crypto from 0.43.0 to 0.43.1
4 months ago
dependabot[bot] 507f4d04d3
Bump go.step.sm/crypto from 0.43.0 to 0.43.1
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.43.0 to 0.43.1.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.43.0...v0.43.1)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
dependabot[bot] 2ffc9081e7
Bump github.com/hashicorp/vault/api/auth/kubernetes from 0.5.0 to 0.6.0
Bumps [github.com/hashicorp/vault/api/auth/kubernetes](https://github.com/hashicorp/vault) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG-v0.md)
- [Commits](https://github.com/hashicorp/vault/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api/auth/kubernetes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
Herman Slatman bb296c9d19
Merge pull request #1708 from smallstep/herman/csr-expires-header
Add `Expires` header to CRL endpoint
4 months ago
Mariano Cano bd99db0071
Merge pull request #1685 from venkyg-sec/allow_custom_tls_config
Allow usage of externally supplied TLS config
4 months ago
Mariano Cano 503e5046ea
Merge branch 'master' into allow_custom_tls_config 4 months ago
Mariano Cano beea482a0c
Fix linter errors in ca/ca.go 4 months ago
Mariano Cano 073fcb7e75
Merge pull request #1684 from venkyg-sec/allow_external_x509_ca_service_intf
Allow x509 Service CA implementation to be injected through ca and authority options
4 months ago
Mariano Cano ac773ff44e
Merge branch 'master' into allow_external_x509_ca_service_intf 4 months ago
Mariano Cano 9fcdd3ffa6
Fix format warnings on ca/ca.go 4 months ago
Herman Slatman 3dbb4aad3d
Change CRL unavailable case to HTTP 404 4 months ago
Herman Slatman 5d865b28d1
Merge pull request #1715 from rvichery/aws-ca-west-1-iid-certificate
Add AWS ca-west-1 identity document certificate
4 months ago
Carl Tashian e542a26d4b
Fix RA installer step-ca package URL 4 months ago
Remi Vichery ee44ac104d
fixup! Add AWS ca-west-1 identity document certificate 4 months ago
github-actions[bot] 490d065c8e
Merge pull request #1713 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api/auth/approle-0.6.0
Bump github.com/hashicorp/vault/api/auth/approle from 0.5.0 to 0.6.0
4 months ago
Remi Vichery 283d46d9a7
Add AWS ca-west-1 identity document certificate 4 months ago
dependabot[bot] a3bed4095a
Bump github.com/hashicorp/vault/api/auth/approle from 0.5.0 to 0.6.0
Bumps [github.com/hashicorp/vault/api/auth/approle](https://github.com/hashicorp/vault) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG-v0.md)
- [Commits](https://github.com/hashicorp/vault/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api/auth/approle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
github-actions[bot] d174e78801
Merge pull request #1712 from smallstep/dependabot/go_modules/golang.org/x/net-0.21.0
Bump golang.org/x/net from 0.20.0 to 0.21.0
4 months ago
github-actions[bot] 5f91441d44
Merge pull request #1711 from smallstep/dependabot/go_modules/cloud.google.com/go/longrunning-0.5.5
Bump cloud.google.com/go/longrunning from 0.5.4 to 0.5.5
4 months ago
dependabot[bot] a32dade78d
Bump golang.org/x/net from 0.20.0 to 0.21.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.21.0.
- [Commits](https://github.com/golang/net/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
dependabot[bot] b9db4e3fa4
Bump cloud.google.com/go/longrunning from 0.5.4 to 0.5.5
Bumps [cloud.google.com/go/longrunning](https://github.com/googleapis/google-cloud-go) from 0.5.4 to 0.5.5.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/longrunning/v0.5.4...longrunning/v0.5.5)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/longrunning
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
Herman Slatman c76dad8a22
Improve tests for CRL HTTP handler 5 months ago
Herman Slatman 69f5f8d8ea
Use `stretchr/testify` instead of `smallstep/assert` for tests 5 months ago
Herman Slatman d1deb7f930
Add `Expires` header to CRL response 5 months ago
github-actions[bot] 67246925d2
Merge pull request #1706 from smallstep/dependabot/go_modules/github.com/prometheus/client_golang-1.18.0
Bump github.com/prometheus/client_golang from 1.15.1 to 1.18.0
5 months ago
github-actions[bot] 6d29e8ade8
Merge pull request #1704 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.43.0
Bump go.step.sm/crypto from 0.42.1 to 0.43.0
5 months ago
github-actions[bot] 05ccf84623
Merge pull request #1705 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.15.5
Bump cloud.google.com/go/security from 1.15.4 to 1.15.5
5 months ago
dependabot[bot] 78522c7544
Bump github.com/prometheus/client_golang from 1.15.1 to 1.18.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.15.1 to 1.18.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.15.1...v1.18.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
5 months ago
dependabot[bot] 053d05b4a1
Bump cloud.google.com/go/security from 1.15.4 to 1.15.5
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.15.4 to 1.15.5.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/kms/v1.15.4...kms/v1.15.5)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/security
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
5 months ago
dependabot[bot] 5209393197
Bump go.step.sm/crypto from 0.42.1 to 0.43.0
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.42.1 to 0.43.0.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.42.1...v0.43.0)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
5 months ago
github-actions[bot] 59ea73129a
Merge pull request #1693 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api-1.11.0
Bump github.com/hashicorp/vault/api from 1.10.0 to 1.11.0
5 months ago
dependabot[bot] 78d889a047
Bump github.com/hashicorp/vault/api from 1.10.0 to 1.11.0
Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/vault/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
5 months ago
github-actions[bot] 2fcb33bd3a
Merge pull request #1695 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.29.1
Bump github.com/newrelic/go-agent/v3 from 3.29.0 to 3.29.1
5 months ago
github-actions[bot] fe926e9ad6
Merge pull request #1694 from smallstep/dependabot/go_modules/github.com/google/uuid-1.6.0
Bump github.com/google/uuid from 1.5.0 to 1.6.0
5 months ago
github-actions[bot] 8123d6a405
Merge pull request #1692 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.42.1
Bump go.step.sm/crypto from 0.42.0 to 0.42.1
5 months ago
dependabot[bot] d9cf8aa685
Bump github.com/newrelic/go-agent/v3 from 3.29.0 to 3.29.1
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.29.0 to 3.29.1.
- [Release notes](https://github.com/newrelic/go-agent/releases)
- [Changelog](https://github.com/newrelic/go-agent/blob/master/CHANGELOG.md)
- [Commits](https://github.com/newrelic/go-agent/compare/v3.29.0...v3.29.1)

---
updated-dependencies:
- dependency-name: github.com/newrelic/go-agent/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
5 months ago
dependabot[bot] eeaabbc481
Bump github.com/google/uuid from 1.5.0 to 1.6.0
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
5 months ago
dependabot[bot] 11220903d2
Bump go.step.sm/crypto from 0.42.0 to 0.42.1
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.42.0 to 0.42.1.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.42.0...v0.42.1)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
5 months ago
Panagiotis Siatras dd1ff9c15b
Implementation of the Prometheus endpoint (#1669)
Implementation of the http://{metricsAddress}/metrics Prometheus endpoint.
5 months ago
Venky Gopal 356e7070ef Allow usage of externally supplied TLS config 5 months ago
Venky Gopal fbc1e895c2 Allow x509 Service CA implementation to be injected through ca and authority options 5 months ago
Herman Slatman 4ef093dc4b
Fix broken tests relying on `Sign` in mocks 9 months ago
Herman Slatman 9e3807eaa3
Use `SignWithContext` in the critical paths 9 months ago
Herman Slatman 4e06bdbc51
Add `SignWithContext` method to authority and mocks 9 months ago
Herman Slatman b2301ea127
Remove the webhook `Do` method 9 months ago
Herman Slatman f3229d3e3c
Propagate (original) request ID to webhook requests
Technically the webhook request is a new request, so maybe the
`X-Request-ID` should not be set to the value of the original
request? But then the original request ID should be propageted
in the webhook request body, or using a different header.

The way the request ID is used in this functionality is actually
more like a tracing ID, so that may be an option too.
9 months ago

@ -6,17 +6,6 @@ permissions:
pull-requests: write pull-requests: write
jobs: jobs:
dependabot: dependabot-auto-merge:
runs-on: ubuntu-latest uses: smallstep/workflows/.github/workflows/dependabot-auto-merge.yml@main
if: ${{ github.actor == 'dependabot[bot]' }} secrets: inherit
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.6.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

@ -45,12 +45,12 @@ jobs:
echo "DOCKER_TAGS_HSM=${{ env.DOCKER_TAGS_HSM }},${{ env.DOCKER_IMAGE }}:hsm" >> "${GITHUB_ENV}" echo "DOCKER_TAGS_HSM=${{ env.DOCKER_TAGS_HSM }},${{ env.DOCKER_IMAGE }}:hsm" >> "${GITHUB_ENV}"
- name: Create Release - name: Create Release
id: create_release id: create_release
uses: actions/create-release@v1 uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87 # v2.0.5
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ github.ref }} tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }} name: Release ${{ github.ref }}
draft: false draft: false
prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }} prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }}

@ -98,7 +98,7 @@ signs:
- cmd: cosign - cmd: cosign
signature: "${artifact}.sig" signature: "${artifact}.sig"
certificate: "${artifact}.pem" certificate: "${artifact}.pem"
args: ["sign-blob", "--oidc-issuer=https://token.actions.githubusercontent.com", "--output-certificate=${certificate}", "--output-signature=${signature}", "${artifact}"] args: ["sign-blob", "--oidc-issuer=https://token.actions.githubusercontent.com", "--output-certificate=${certificate}", "--output-signature=${signature}", "${artifact}", "--yes"]
artifacts: all artifacts: all
snapshot: snapshot:
@ -180,7 +180,7 @@ release:
Those were the changes on {{ .Tag }}! Those were the changes on {{ .Tag }}!
Come join us on [Discord](https://discord.gg/X2RKGwEbV9) to ask questions, chat about PKI, or get a sneak peak at the freshest PKI memes. Come join us on [Discord](https://discord.gg/X2RKGwEbV9) to ask questions, chat about PKI, or get a sneak peek at the freshest PKI memes.
# You can disable this pipe in order to not upload any artifacts. # You can disable this pipe in order to not upload any artifacts.
# Defaults to false. # Defaults to false.
@ -268,7 +268,7 @@ winget:
# Release notes URL. # Release notes URL.
# #
# Templates: allowed # Templates: allowed
release_notes_url: "https://github.com/smallstep/certificates/releases/tag/{{.Version}}" release_notes_url: "https://github.com/smallstep/certificates/releases/tag/{{ .Tag }}"
# Create the PR - for testing # Create the PR - for testing
skip_upload: auto skip_upload: auto
@ -283,7 +283,7 @@ winget:
repository: repository:
owner: smallstep owner: smallstep
name: winget-pkgs name: winget-pkgs
branch: step branch: "step-ca-{{.Version}}"
# Optionally a token can be provided, if it differs from the token # Optionally a token can be provided, if it differs from the token
# provided to GoReleaser # provided to GoReleaser

@ -25,6 +25,63 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
--- ---
## [0.26.2] - 2024-06-13
### Added
- Add provisionerID to ACME accounts (smallstep/certificates#1830)
- Enable verifying ACME provisioner using provisionerID if available (smallstep/certificates#1844)
- Add methods to Authority to get intermediate certificates (smallstep/certificates#1848)
- Add GetX509Signer method (smallstep/certificates#1850)
### Changed
- Make ISErrNotFound more flexible (smallstep/certificates#1819)
- Log errors using slog.Logger (smallstep/certificates#1849)
- Update hardcoded AWS certificates (smallstep/certificates#1881)
## [0.26.1] - 2024-04-22
### Added
- Allow configuration of a custom SCEP key manager (smallstep/certificates#1797)
### Fixed
- id-scep-failInfoText OID (smallstep/certificates#1794)
- CA startup with Vault RA configuration (smallstep/certificates#1803)
## [0.26.0] - 2024-03-28
### Added
- [TPM KMS](https://github.com/smallstep/crypto/tree/master/kms/tpmkms) support for CA keys (smallstep/certificates#1772)
- Propagation of HTTP request identifier using X-Request-Id header (smallstep/certificates#1743, smallstep/certificates#1542)
- Expires header in CRL response (smallstep/certificates#1708)
- Support for providing TLS configuration programmatically (smallstep/certificates#1685)
- Support for providing external CAS implementation (smallstep/certificates#1684)
- AWS `ca-west-1` identity document root certificate (smallstep/certificates#1715)
- [COSE RS1](https://www.rfc-editor.org/rfc/rfc8812.html#section-2) as a supported algorithm with ACME `device-attest-01` challenge (smallstep/certificates#1663)
### Changed
- In an RA setup, let the CA decide the RA certificate lifetime (smallstep/certificates#1764)
- Use Debian Bookworm in Docker containers (smallstep/certificates#1615)
- Error message for CSR validation (smallstep/certificates#1665)
- Updated dependencies
### Fixed
- Stop CA when any of the required servers fails to start (smallstep/certificates#1751). Before the fix, the CA would continue running and only log the server failure when stopped.
- Configuration loading errors when not using context were not returned. Fixed in [cli-utils/109](https://github.com/smallstep/cli-utils/pull/109).
- HTTP_PROXY and HTTPS_PROXY support for ACME validation client (smallstep/certificates#1658).
### Security
- Upgrade to using cosign v2 for signing artifacts
## [0.25.1] - 2023-11-28 ## [0.25.1] - 2023-11-28
### Added ### Added
@ -36,7 +93,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Generation of first provisioner name on `step ca init` in (smallstep/certificates#1566) - Generation of first provisioner name on `step ca init` in (smallstep/certificates#1566)
- Processing of SCEP Get PKIOperation requests in (smallstep/certificates#1570) - Processing of SCEP Get PKIOperation requests in (smallstep/certificates#1570)
- Support for signing identity certificate during SSH sign by skipping URI validation in (smallstep/certificates#1572) - Support for signing identity certificate during SSH sign by skipping URI validation in (smallstep/certificates#1572)
- Dependency on `micromdm/scep` and `go.mozilla.org/pkcs7` to use Smallstep forks in (smallstep/certificates#1600) - Dependency on `micromdm/scep` and `go.mozilla.org/pkcs7` to use Smallstep forks in (smallstep/certificates#1600)
- Make the Common Name validator for JWK provisioners accept values from SANs too in (smallstep/certificates#1609) - Make the Common Name validator for JWK provisioners accept values from SANs too in (smallstep/certificates#1609)

@ -1,49 +1,62 @@
# Step Certificates # step-ca
`step-ca` is an online certificate authority for secure, automated certificate management. It's the server counterpart to the [`step` CLI tool](https://github.com/smallstep/cli). [![GitHub release](https://img.shields.io/github/release/smallstep/certificates.svg)](https://github.com/smallstep/certificates/releases/latest)
[![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/certificates)](https://goreportcard.com/report/github.com/smallstep/certificates)
[![Build Status](https://github.com/smallstep/certificates/actions/workflows/test.yml/badge.svg)](https://github.com/smallstep/certificates)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![CLA assistant](https://cla-assistant.io/readme/badge/smallstep/certificates)](https://cla-assistant.io/smallstep/certificates)
You can use it to: `step-ca` is an online certificate authority for secure, automated certificate management for DevOps.
- Issue X.509 certificates for your internal infrastructure: It's the server counterpart to the [`step` CLI tool](https://github.com/smallstep/cli) for working with certificates and keys.
- HTTPS certificates that [work in browsers](https://smallstep.com/blog/step-v0-8-6-valid-HTTPS-certificates-for-dev-pre-prod.html) ([RFC5280](https://tools.ietf.org/html/rfc5280) and [CA/Browser Forum](https://cabforum.org/baseline-requirements-documents/) compliance) Both projects are maintained by [Smallstep Labs](https://smallstep.com).
- TLS certificates for VMs, containers, APIs, mobile clients, database connections, printers, wifi networks, toaster ovens...
- Client certificates to [enable mutual TLS (mTLS)](https://smallstep.com/hello-mtls) in your infra. mTLS is an optional feature in TLS where both client and server authenticate each other. Why add the complexity of a VPN when you can safely use mTLS over the public internet? You can use `step-ca` to:
- Issue HTTPS server and client certificates that [work in browsers](https://smallstep.com/blog/step-v0-8-6-valid-HTTPS-certificates-for-dev-pre-prod.html) ([RFC5280](https://tools.ietf.org/html/rfc5280) and [CA/Browser Forum](https://cabforum.org/baseline-requirements-documents/) compliance)
- Issue TLS certificates for DevOps: VMs, containers, APIs, database connections, Kubernetes pods...
- Issue SSH certificates: - Issue SSH certificates:
- For people, in exchange for single sign-on ID tokens - For people, in exchange for single sign-on identity tokens
- For hosts, in exchange for cloud instance identity documents - For hosts, in exchange for cloud instance identity documents
- Easily automate certificate management: - Easily automate certificate management:
- It's an ACME v2 server - It's an [ACME server](https://smallstep.com/docs/step-ca/acme-basics/) that supports all [popular ACME challenge types](https://smallstep.com/docs/step-ca/acme-basics/#acme-challenge-types)
- It has a JSON API
- It comes with a [Go wrapper](./examples#user-content-basic-client-usage) - It comes with a [Go wrapper](./examples#user-content-basic-client-usage)
- ... and there's a [command-line client](https://github.com/smallstep/cli) you can use in scripts! - ... and there's a [command-line client](https://github.com/smallstep/cli) you can use in scripts!
Whatever your use case, `step-ca` is easy to use and hard to misuse, thanks to [safe, sane defaults](https://smallstep.com/docs/step-ca/certificate-authority-server-production#sane-cryptographic-defaults).
--- ---
**Don't want to run your own CA?** ### Comparison with Smallstep's commercial product
To get up and running quickly, or as an alternative to running your own `step-ca` server, consider creating a [free hosted smallstep Certificate Manager authority](https://info.smallstep.com/certificate-manager-early-access-mvp/).
`step-ca` is optimized for a two-tier PKI serving common DevOps use cases.
As you design your PKI, if you need any of the following, [consider our commerical CA](http://smallstep.com):
- Multiple certificate authorities
- Active revocation (CRL, OSCP)
- Turnkey high-volume, high availability CA
- An API for seamless IaC management of your PKI
- Integrated support for SCEP & NDES, for migrating from legacy Active Directory Certificate Services deployments
- Device identity — cross-platform device inventory and attestation using Secure Enclave & TPM 2.0
- Highly automated PKI — managed certificate renewal, monitoring, TPM-based attested enrollment
- Seamless client deployments of EAP-TLS Wi-Fi, VPN, SSH, and browser certificates
- Jamf, Intune, or other MDM for root distribution and client enrollment
- Web Admin UI — history, issuance, and metrics
- ACME External Account Binding (EAB)
- Deep integration with an identity provider
- Fine-grained, role-based access control
- FIPS-compliant software
- HSM-bound private keys
See our [full feature comparison](https://smallstep.com/step-ca-vs-smallstep-certificate-manager/) for more.
You can [start a free trial](https://smallstep.com/signup) or [set up a call with us](https://go.smallstep.com/request-demo) to learn more.
--- ---
**Questions? Find us in [Discussions](https://github.com/smallstep/certificates/discussions) or [Join our Discord](https://u.step.sm/discord).** **Questions? Find us in [Discussions](https://github.com/smallstep/certificates/discussions) or [Join our Discord](https://u.step.sm/discord).**
[Website](https://smallstep.com/certificates) | [Website](https://smallstep.com/certificates) |
[Documentation](https://smallstep.com/docs) | [Documentation](https://smallstep.com/docs/step-ca) |
[Installation](https://smallstep.com/docs/step-ca/installation) | [Installation](https://smallstep.com/docs/step-ca/installation) |
[Getting Started](https://smallstep.com/docs/step-ca/getting-started) |
[Contributor's Guide](./CONTRIBUTING.md) [Contributor's Guide](./CONTRIBUTING.md)
[![GitHub release](https://img.shields.io/github/release/smallstep/certificates.svg)](https://github.com/smallstep/certificates/releases/latest)
[![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/certificates)](https://goreportcard.com/report/github.com/smallstep/certificates)
[![Build Status](https://github.com/smallstep/certificates/actions/workflows/test.yml/badge.svg)](https://github.com/smallstep/certificates)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![CLA assistant](https://cla-assistant.io/readme/badge/smallstep/certificates)](https://cla-assistant.io/smallstep/certificates)
[![GitHub stars](https://img.shields.io/github/stars/smallstep/certificates.svg?style=social)](https://github.com/smallstep/certificates/stargazers)
[![Twitter followers](https://img.shields.io/twitter/follow/smallsteplabs.svg?label=Follow&style=social)](https://twitter.com/intent/follow?screen_name=smallsteplabs)
![star us](https://github.com/smallstep/certificates/raw/master/docs/images/star.gif)
## Features ## Features
### 🦾 A fast, stable, flexible private CA ### 🦾 A fast, stable, flexible private CA
@ -52,7 +65,6 @@ Setting up a *public key infrastructure* (PKI) is out of reach for many small te
- Choose key types (RSA, ECDSA, EdDSA) and lifetimes to suit your needs - Choose key types (RSA, ECDSA, EdDSA) and lifetimes to suit your needs
- [Short-lived certificates](https://smallstep.com/blog/passive-revocation.html) with automated enrollment, renewal, and passive revocation - [Short-lived certificates](https://smallstep.com/blog/passive-revocation.html) with automated enrollment, renewal, and passive revocation
- Capable of high availability (HA) deployment using [root federation](https://smallstep.com/blog/step-v0.8.3-federation-root-rotation.html) and/or multiple intermediaries
- Can operate as [an online intermediate CA for an existing root CA](https://smallstep.com/docs/tutorials/intermediate-ca-new-ca) - Can operate as [an online intermediate CA for an existing root CA](https://smallstep.com/docs/tutorials/intermediate-ca-new-ca)
- [Badger, BoltDB, Postgres, and MySQL database backends](https://smallstep.com/docs/step-ca/configuration#databases) - [Badger, BoltDB, Postgres, and MySQL database backends](https://smallstep.com/docs/step-ca/configuration#databases)
@ -127,5 +139,5 @@ and visiting http://localhost:8080.
## Feedback? ## Feedback?
* Tell us what you like and don't like about managing your PKI - we're eager to help solve problems in this space. * Tell us what you like and don't like about managing your PKI - we're eager to help solve problems in this space. [Join our Discord](https://u.step.sm/discord) or [GitHub Discussions](https://github.com/smallstep/certificates/discussions)
* Tell us about a feature you'd like to see! [Add a feature request Issue](https://github.com/smallstep/certificates/issues/new?assignees=&labels=enhancement%2C+needs+triage&template=enhancement.md&title=), [ask on Discussions](https://github.com/smallstep/certificates/discussions), or hit us up on [Twitter](https://twitter.com/smallsteplabs). * Tell us about a feature you'd like to see! [Request a Feature](https://github.com/smallstep/certificates/issues/new?assignees=&labels=enhancement%2C+needs+triage&template=enhancement.md&title=)

@ -21,6 +21,7 @@ type Account struct {
OrdersURL string `json:"orders"` OrdersURL string `json:"orders"`
ExternalAccountBinding interface{} `json:"externalAccountBinding,omitempty"` ExternalAccountBinding interface{} `json:"externalAccountBinding,omitempty"`
LocationPrefix string `json:"-"` LocationPrefix string `json:"-"`
ProvisionerID string `json:"-"`
ProvisionerName string `json:"-"` ProvisionerName string `json:"-"`
} }

@ -82,23 +82,23 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
payload, err := payloadFromContext(ctx) payload, err := payloadFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
var nar NewAccountRequest var nar NewAccountRequest
if err := json.Unmarshal(payload.value, &nar); err != nil { if err := json.Unmarshal(payload.value, &nar); err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal new-account request payload")) "failed to unmarshal new-account request payload"))
return return
} }
if err := nar.Validate(); err != nil { if err := nar.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
prov, err := acmeProvisionerFromContext(ctx) prov, err := acmeProvisionerFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -108,26 +108,26 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
var acmeErr *acme.Error var acmeErr *acme.Error
if !errors.As(err, &acmeErr) || acmeErr.Status != http.StatusBadRequest { if !errors.As(err, &acmeErr) || acmeErr.Status != http.StatusBadRequest {
// Something went wrong ... // Something went wrong ...
render.Error(w, err) render.Error(w, r, err)
return return
} }
// Account does not exist // // Account does not exist //
if nar.OnlyReturnExisting { if nar.OnlyReturnExisting {
render.Error(w, acme.NewError(acme.ErrorAccountDoesNotExistType, render.Error(w, r, acme.NewError(acme.ErrorAccountDoesNotExistType,
"account does not exist")) "account does not exist"))
return return
} }
jwk, err := jwkFromContext(ctx) jwk, err := jwkFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
eak, err := validateExternalAccountBinding(ctx, &nar) eak, err := validateExternalAccountBinding(ctx, &nar)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -136,20 +136,21 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
Contact: nar.Contact, Contact: nar.Contact,
Status: acme.StatusValid, Status: acme.StatusValid,
LocationPrefix: getAccountLocationPath(ctx, linker, ""), LocationPrefix: getAccountLocationPath(ctx, linker, ""),
ProvisionerName: prov.GetName(), ProvisionerID: prov.ID,
ProvisionerName: prov.Name,
} }
if err := db.CreateAccount(ctx, acc); err != nil { if err := db.CreateAccount(ctx, acc); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error creating account")) render.Error(w, r, acme.WrapErrorISE(err, "error creating account"))
return return
} }
if eak != nil { // means that we have a (valid) External Account Binding key that should be bound, updated and sent in the response if eak != nil { // means that we have a (valid) External Account Binding key that should be bound, updated and sent in the response
if err := eak.BindTo(acc); err != nil { if err := eak.BindTo(acc); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
if err := db.UpdateExternalAccountKey(ctx, prov.ID, eak); err != nil { if err := db.UpdateExternalAccountKey(ctx, prov.ID, eak); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error updating external account binding key")) render.Error(w, r, acme.WrapErrorISE(err, "error updating external account binding key"))
return return
} }
acc.ExternalAccountBinding = nar.ExternalAccountBinding acc.ExternalAccountBinding = nar.ExternalAccountBinding
@ -162,7 +163,7 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
linker.LinkAccount(ctx, acc) linker.LinkAccount(ctx, acc)
w.Header().Set("Location", getAccountLocationPath(ctx, linker, acc.ID)) w.Header().Set("Location", getAccountLocationPath(ctx, linker, acc.ID))
render.JSONStatus(w, acc, httpStatus) render.JSONStatus(w, r, acc, httpStatus)
} }
// GetOrUpdateAccount is the api for updating an ACME account. // GetOrUpdateAccount is the api for updating an ACME account.
@ -173,12 +174,12 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx) acc, err := accountFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
payload, err := payloadFromContext(ctx) payload, err := payloadFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -187,12 +188,12 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
if !payload.isPostAsGet { if !payload.isPostAsGet {
var uar UpdateAccountRequest var uar UpdateAccountRequest
if err := json.Unmarshal(payload.value, &uar); err != nil { if err := json.Unmarshal(payload.value, &uar); err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal new-account request payload")) "failed to unmarshal new-account request payload"))
return return
} }
if err := uar.Validate(); err != nil { if err := uar.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
if len(uar.Status) > 0 || len(uar.Contact) > 0 { if len(uar.Status) > 0 || len(uar.Contact) > 0 {
@ -203,7 +204,7 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
} }
if err := db.UpdateAccount(ctx, acc); err != nil { if err := db.UpdateAccount(ctx, acc); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error updating account")) render.Error(w, r, acme.WrapErrorISE(err, "error updating account"))
return return
} }
} }
@ -212,7 +213,7 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
linker.LinkAccount(ctx, acc) linker.LinkAccount(ctx, acc)
w.Header().Set("Location", linker.GetLink(ctx, acme.AccountLinkType, acc.ID)) w.Header().Set("Location", linker.GetLink(ctx, acme.AccountLinkType, acc.ID))
render.JSON(w, acc) render.JSON(w, r, acc)
} }
func logOrdersByAccount(w http.ResponseWriter, oids []string) { func logOrdersByAccount(w http.ResponseWriter, oids []string) {
@ -232,23 +233,23 @@ func GetOrdersByAccountID(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx) acc, err := accountFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
accID := chi.URLParam(r, "accID") accID := chi.URLParam(r, "accID")
if acc.ID != accID { if acc.ID != accID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account ID '%s' does not match url param '%s'", acc.ID, accID)) render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account ID '%s' does not match url param '%s'", acc.ID, accID))
return return
} }
orders, err := db.GetOrdersByAccountID(ctx, acc.ID) orders, err := db.GetOrdersByAccountID(ctx, acc.ID)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
linker.LinkOrdersByAccountID(ctx, orders) linker.LinkOrdersByAccountID(ctx, orders)
render.JSON(w, orders) render.JSON(w, r, orders)
logOrdersByAccount(w, orders) logOrdersByAccount(w, orders)
} }

@ -14,6 +14,7 @@ import (
"time" "time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/pkg/errors" "github.com/pkg/errors"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
@ -66,6 +67,19 @@ func newProv() acme.Provisioner {
return p return p
} }
func newProvWithID() acme.Provisioner {
// Initialize provisioners
p := &provisioner.ACME{
ID: uuid.NewString(),
Type: "ACME",
Name: "test@acme-<test>provisioner.com",
}
if err := p.Init(provisioner.Config{Claims: globalProvisionerClaims}); err != nil {
fmt.Printf("%v", err)
}
return p
}
func newProvWithOptions(options *provisioner.Options) acme.Provisioner { func newProvWithOptions(options *provisioner.Options) acme.Provisioner {
// Initialize provisioners // Initialize provisioners
p := &provisioner.ACME{ p := &provisioner.ACME{

@ -223,13 +223,13 @@ func GetDirectory(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
acmeProv, err := acmeProvisionerFromContext(ctx) acmeProv, err := acmeProvisionerFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
linker := acme.MustLinkerFromContext(ctx) linker := acme.MustLinkerFromContext(ctx)
render.JSON(w, &Directory{ render.JSON(w, r, &Directory{
NewNonce: linker.GetLink(ctx, acme.NewNonceLinkType), NewNonce: linker.GetLink(ctx, acme.NewNonceLinkType),
NewAccount: linker.GetLink(ctx, acme.NewAccountLinkType), NewAccount: linker.GetLink(ctx, acme.NewAccountLinkType),
NewOrder: linker.GetLink(ctx, acme.NewOrderLinkType), NewOrder: linker.GetLink(ctx, acme.NewOrderLinkType),
@ -273,8 +273,8 @@ func shouldAddMetaObject(p *provisioner.ACME) bool {
// NotImplemented returns a 501 and is generally a placeholder for functionality which // NotImplemented returns a 501 and is generally a placeholder for functionality which
// MAY be added at some point in the future but is not in any way a guarantee of such. // MAY be added at some point in the future but is not in any way a guarantee of such.
func NotImplemented(w http.ResponseWriter, _ *http.Request) { func NotImplemented(w http.ResponseWriter, r *http.Request) {
render.Error(w, acme.NewError(acme.ErrorNotImplementedType, "this API is not implemented")) render.Error(w, r, acme.NewError(acme.ErrorNotImplementedType, "this API is not implemented"))
} }
// GetAuthorization ACME api for retrieving an Authz. // GetAuthorization ACME api for retrieving an Authz.
@ -285,28 +285,28 @@ func GetAuthorization(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx) acc, err := accountFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
az, err := db.GetAuthorization(ctx, chi.URLParam(r, "authzID")) az, err := db.GetAuthorization(ctx, chi.URLParam(r, "authzID"))
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving authorization")) render.Error(w, r, acme.WrapErrorISE(err, "error retrieving authorization"))
return return
} }
if acc.ID != az.AccountID { if acc.ID != az.AccountID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own authorization '%s'", acc.ID, az.ID)) "account '%s' does not own authorization '%s'", acc.ID, az.ID))
return return
} }
if err = az.UpdateStatus(ctx, db); err != nil { if err = az.UpdateStatus(ctx, db); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error updating authorization status")) render.Error(w, r, acme.WrapErrorISE(err, "error updating authorization status"))
return return
} }
linker.LinkAuthorization(ctx, az) linker.LinkAuthorization(ctx, az)
w.Header().Set("Location", linker.GetLink(ctx, acme.AuthzLinkType, az.ID)) w.Header().Set("Location", linker.GetLink(ctx, acme.AuthzLinkType, az.ID))
render.JSON(w, az) render.JSON(w, r, az)
} }
// GetChallenge ACME api for retrieving a Challenge. // GetChallenge ACME api for retrieving a Challenge.
@ -317,13 +317,13 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx) acc, err := accountFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
payload, err := payloadFromContext(ctx) payload, err := payloadFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -336,22 +336,22 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) {
azID := chi.URLParam(r, "authzID") azID := chi.URLParam(r, "authzID")
ch, err := db.GetChallenge(ctx, chi.URLParam(r, "chID"), azID) ch, err := db.GetChallenge(ctx, chi.URLParam(r, "chID"), azID)
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving challenge")) render.Error(w, r, acme.WrapErrorISE(err, "error retrieving challenge"))
return return
} }
ch.AuthorizationID = azID ch.AuthorizationID = azID
if acc.ID != ch.AccountID { if acc.ID != ch.AccountID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own challenge '%s'", acc.ID, ch.ID)) "account '%s' does not own challenge '%s'", acc.ID, ch.ID))
return return
} }
jwk, err := jwkFromContext(ctx) jwk, err := jwkFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
if err = ch.Validate(ctx, db, jwk, payload.value); err != nil { if err = ch.Validate(ctx, db, jwk, payload.value); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error validating challenge")) render.Error(w, r, acme.WrapErrorISE(err, "error validating challenge"))
return return
} }
@ -359,7 +359,7 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Link", link(linker.GetLink(ctx, acme.AuthzLinkType, azID), "up")) w.Header().Add("Link", link(linker.GetLink(ctx, acme.AuthzLinkType, azID), "up"))
w.Header().Set("Location", linker.GetLink(ctx, acme.ChallengeLinkType, azID, ch.ID)) w.Header().Set("Location", linker.GetLink(ctx, acme.ChallengeLinkType, azID, ch.ID))
render.JSON(w, ch) render.JSON(w, r, ch)
} }
// GetCertificate ACME api for retrieving a Certificate. // GetCertificate ACME api for retrieving a Certificate.
@ -369,18 +369,18 @@ func GetCertificate(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx) acc, err := accountFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
certID := chi.URLParam(r, "certID") certID := chi.URLParam(r, "certID")
cert, err := db.GetCertificate(ctx, certID) cert, err := db.GetCertificate(ctx, certID)
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving certificate")) render.Error(w, r, acme.WrapErrorISE(err, "error retrieving certificate"))
return return
} }
if cert.AccountID != acc.ID { if cert.AccountID != acc.ID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own certificate '%s'", acc.ID, certID)) "account '%s' does not own certificate '%s'", acc.ID, certID))
return return
} }

@ -36,7 +36,7 @@ func addNonce(next nextHTTP) nextHTTP {
db := acme.MustDatabaseFromContext(r.Context()) db := acme.MustDatabaseFromContext(r.Context())
nonce, err := db.CreateNonce(r.Context()) nonce, err := db.CreateNonce(r.Context())
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
w.Header().Set("Replay-Nonce", string(nonce)) w.Header().Set("Replay-Nonce", string(nonce))
@ -64,7 +64,7 @@ func verifyContentType(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
p, err := provisionerFromContext(r.Context()) p, err := provisionerFromContext(r.Context())
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -88,7 +88,7 @@ func verifyContentType(next nextHTTP) nextHTTP {
return return
} }
} }
render.Error(w, acme.NewError(acme.ErrorMalformedType, render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
"expected content-type to be in %s, but got %s", expected, ct)) "expected content-type to be in %s, but got %s", expected, ct))
} }
} }
@ -98,12 +98,12 @@ func parseJWS(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body) body, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "failed to read request body")) render.Error(w, r, acme.WrapErrorISE(err, "failed to read request body"))
return return
} }
jws, err := jose.ParseJWS(string(body)) jws, err := jose.ParseJWS(string(body))
if err != nil { if err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "failed to parse JWS from request body")) render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "failed to parse JWS from request body"))
return return
} }
ctx := context.WithValue(r.Context(), jwsContextKey, jws) ctx := context.WithValue(r.Context(), jwsContextKey, jws)
@ -133,26 +133,26 @@ func validateJWS(next nextHTTP) nextHTTP {
jws, err := jwsFromContext(ctx) jws, err := jwsFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
if len(jws.Signatures) == 0 { if len(jws.Signatures) == 0 {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "request body does not contain a signature")) render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "request body does not contain a signature"))
return return
} }
if len(jws.Signatures) > 1 { if len(jws.Signatures) > 1 {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "request body contains more than one signature")) render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "request body contains more than one signature"))
return return
} }
sig := jws.Signatures[0] sig := jws.Signatures[0]
uh := sig.Unprotected uh := sig.Unprotected
if len(uh.KeyID) > 0 || if uh.KeyID != "" ||
uh.JSONWebKey != nil || uh.JSONWebKey != nil ||
len(uh.Algorithm) > 0 || uh.Algorithm != "" ||
len(uh.Nonce) > 0 || uh.Nonce != "" ||
len(uh.ExtraHeaders) > 0 { len(uh.ExtraHeaders) > 0 {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "unprotected header must not be used")) render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "unprotected header must not be used"))
return return
} }
hdr := sig.Protected hdr := sig.Protected
@ -162,13 +162,13 @@ func validateJWS(next nextHTTP) nextHTTP {
switch k := hdr.JSONWebKey.Key.(type) { switch k := hdr.JSONWebKey.Key.(type) {
case *rsa.PublicKey: case *rsa.PublicKey:
if k.Size() < keyutil.MinRSAKeyBytes { if k.Size() < keyutil.MinRSAKeyBytes {
render.Error(w, acme.NewError(acme.ErrorMalformedType, render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
"rsa keys must be at least %d bits (%d bytes) in size", "rsa keys must be at least %d bits (%d bytes) in size",
8*keyutil.MinRSAKeyBytes, keyutil.MinRSAKeyBytes)) 8*keyutil.MinRSAKeyBytes, keyutil.MinRSAKeyBytes))
return return
} }
default: default:
render.Error(w, acme.NewError(acme.ErrorMalformedType, render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
"jws key type and algorithm do not match")) "jws key type and algorithm do not match"))
return return
} }
@ -176,35 +176,35 @@ func validateJWS(next nextHTTP) nextHTTP {
case jose.ES256, jose.ES384, jose.ES512, jose.EdDSA: case jose.ES256, jose.ES384, jose.ES512, jose.EdDSA:
// we good // we good
default: default:
render.Error(w, acme.NewError(acme.ErrorBadSignatureAlgorithmType, "unsuitable algorithm: %s", hdr.Algorithm)) render.Error(w, r, acme.NewError(acme.ErrorBadSignatureAlgorithmType, "unsuitable algorithm: %s", hdr.Algorithm))
return return
} }
// Check the validity/freshness of the Nonce. // Check the validity/freshness of the Nonce.
if err := db.DeleteNonce(ctx, acme.Nonce(hdr.Nonce)); err != nil { if err := db.DeleteNonce(ctx, acme.Nonce(hdr.Nonce)); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
// Check that the JWS url matches the requested url. // Check that the JWS url matches the requested url.
jwsURL, ok := hdr.ExtraHeaders["url"].(string) jwsURL, ok := hdr.ExtraHeaders["url"].(string)
if !ok { if !ok {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "jws missing url protected header")) render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jws missing url protected header"))
return return
} }
reqURL := &url.URL{Scheme: "https", Host: r.Host, Path: r.URL.Path} reqURL := &url.URL{Scheme: "https", Host: r.Host, Path: r.URL.Path}
if jwsURL != reqURL.String() { if jwsURL != reqURL.String() {
render.Error(w, acme.NewError(acme.ErrorMalformedType, render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
"url header in JWS (%s) does not match request url (%s)", jwsURL, reqURL)) "url header in JWS (%s) does not match request url (%s)", jwsURL, reqURL))
return return
} }
if hdr.JSONWebKey != nil && len(hdr.KeyID) > 0 { if hdr.JSONWebKey != nil && hdr.KeyID != "" {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "jwk and kid are mutually exclusive")) render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jwk and kid are mutually exclusive"))
return return
} }
if hdr.JSONWebKey == nil && hdr.KeyID == "" { if hdr.JSONWebKey == nil && hdr.KeyID == "" {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "either jwk or kid must be defined in jws protected header")) render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "either jwk or kid must be defined in jws protected header"))
return return
} }
next(w, r) next(w, r)
@ -221,23 +221,23 @@ func extractJWK(next nextHTTP) nextHTTP {
jws, err := jwsFromContext(ctx) jws, err := jwsFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
jwk := jws.Signatures[0].Protected.JSONWebKey jwk := jws.Signatures[0].Protected.JSONWebKey
if jwk == nil { if jwk == nil {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "jwk expected in protected header")) render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jwk expected in protected header"))
return return
} }
if !jwk.Valid() { if !jwk.Valid() {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "invalid jwk in protected header")) render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "invalid jwk in protected header"))
return return
} }
// Overwrite KeyID with the JWK thumbprint. // Overwrite KeyID with the JWK thumbprint.
jwk.KeyID, err = acme.KeyToID(jwk) jwk.KeyID, err = acme.KeyToID(jwk)
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error getting KeyID from JWK")) render.Error(w, r, acme.WrapErrorISE(err, "error getting KeyID from JWK"))
return return
} }
@ -247,15 +247,15 @@ func extractJWK(next nextHTTP) nextHTTP {
// Get Account OR continue to generate a new one OR continue Revoke with certificate private key // Get Account OR continue to generate a new one OR continue Revoke with certificate private key
acc, err := db.GetAccountByKeyID(ctx, jwk.KeyID) acc, err := db.GetAccountByKeyID(ctx, jwk.KeyID)
switch { switch {
case errors.Is(err, acme.ErrNotFound): case acme.IsErrNotFound(err):
// For NewAccount and Revoke requests ... // For NewAccount and Revoke requests ...
break break
case err != nil: case err != nil:
render.Error(w, err) render.Error(w, r, err)
return return
default: default:
if !acc.IsValid() { if !acc.IsValid() {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account is not active")) render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
return return
} }
ctx = context.WithValue(ctx, accContextKey, acc) ctx = context.WithValue(ctx, accContextKey, acc)
@ -274,11 +274,11 @@ func checkPrerequisites(next nextHTTP) nextHTTP {
if ok { if ok {
ok, err := checkFunc(ctx) ok, err := checkFunc(ctx)
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error checking acme provisioner prerequisites")) render.Error(w, r, acme.WrapErrorISE(err, "error checking acme provisioner prerequisites"))
return return
} }
if !ok { if !ok {
render.Error(w, acme.NewError(acme.ErrorNotImplementedType, "acme provisioner configuration lacks prerequisites")) render.Error(w, r, acme.NewError(acme.ErrorNotImplementedType, "acme provisioner configuration lacks prerequisites"))
return return
} }
} }
@ -296,13 +296,13 @@ func lookupJWK(next nextHTTP) nextHTTP {
jws, err := jwsFromContext(ctx) jws, err := jwsFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
kid := jws.Signatures[0].Protected.KeyID kid := jws.Signatures[0].Protected.KeyID
if kid == "" { if kid == "" {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'")) render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'"))
return return
} }
@ -310,14 +310,14 @@ func lookupJWK(next nextHTTP) nextHTTP {
acc, err := db.GetAccount(ctx, accID) acc, err := db.GetAccount(ctx, accID)
switch { switch {
case acme.IsErrNotFound(err): case acme.IsErrNotFound(err):
render.Error(w, acme.NewError(acme.ErrorAccountDoesNotExistType, "account with ID '%s' not found", accID)) render.Error(w, r, acme.NewError(acme.ErrorAccountDoesNotExistType, "account with ID '%s' not found", accID))
return return
case err != nil: case err != nil:
render.Error(w, err) render.Error(w, r, err)
return return
default: default:
if !acc.IsValid() { if !acc.IsValid() {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account is not active")) render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
return return
} }
@ -325,7 +325,7 @@ func lookupJWK(next nextHTTP) nextHTTP {
if kid != storedLocation { if kid != storedLocation {
// ACME accounts should have a stored location equivalent to the // ACME accounts should have a stored location equivalent to the
// kid in the ACME request. // kid in the ACME request.
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"kid does not match stored account location; expected %s, but got %s", "kid does not match stored account location; expected %s, but got %s",
storedLocation, kid)) storedLocation, kid))
return return
@ -334,14 +334,16 @@ func lookupJWK(next nextHTTP) nextHTTP {
// Verify that the provisioner with which the account was created // Verify that the provisioner with which the account was created
// matches the provisioner in the request URL. // matches the provisioner in the request URL.
reqProv := acme.MustProvisionerFromContext(ctx) reqProv := acme.MustProvisionerFromContext(ctx)
reqProvName := reqProv.GetName() switch {
accProvName := acc.ProvisionerName case acc.ProvisionerID == "" && acc.ProvisionerName != reqProv.GetName():
if reqProvName != accProvName { render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
// Provisioner in the URL must match the provisioner with "account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
// which the account was created. acc.ProvisionerName, reqProv.GetName()))
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, return
case acc.ProvisionerID != "" && acc.ProvisionerID != reqProv.GetID():
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s", "account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
accProvName, reqProvName)) acc.ProvisionerID, reqProv.GetID()))
return return
} }
} else { } else {
@ -353,7 +355,7 @@ func lookupJWK(next nextHTTP) nextHTTP {
linker := acme.MustLinkerFromContext(ctx) linker := acme.MustLinkerFromContext(ctx)
kidPrefix := linker.GetLink(ctx, acme.AccountLinkType, "") kidPrefix := linker.GetLink(ctx, acme.AccountLinkType, "")
if !strings.HasPrefix(kid, kidPrefix) { if !strings.HasPrefix(kid, kidPrefix) {
render.Error(w, acme.NewError(acme.ErrorMalformedType, render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
"kid does not have required prefix; expected %s, but got %s", "kid does not have required prefix; expected %s, but got %s",
kidPrefix, kid)) kidPrefix, kid))
return return
@ -374,7 +376,7 @@ func extractOrLookupJWK(next nextHTTP) nextHTTP {
ctx := r.Context() ctx := r.Context()
jws, err := jwsFromContext(ctx) jws, err := jwsFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -410,16 +412,16 @@ func verifyAndExtractJWSPayload(next nextHTTP) nextHTTP {
ctx := r.Context() ctx := r.Context()
jws, err := jwsFromContext(ctx) jws, err := jwsFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
jwk, err := jwkFromContext(ctx) jwk, err := jwkFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
if jwk.Algorithm != "" && jwk.Algorithm != jws.Signatures[0].Protected.Algorithm { if jwk.Algorithm != "" && jwk.Algorithm != jws.Signatures[0].Protected.Algorithm {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "verifier and signature algorithm do not match")) render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "verifier and signature algorithm do not match"))
return return
} }
@ -428,11 +430,11 @@ func verifyAndExtractJWSPayload(next nextHTTP) nextHTTP {
case errors.Is(err, jose.ErrCryptoFailure): case errors.Is(err, jose.ErrCryptoFailure):
payload, err = retryVerificationWithPatchedSignatures(jws, jwk) payload, err = retryVerificationWithPatchedSignatures(jws, jwk)
if err != nil { if err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws with patched signature(s)")) render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws with patched signature(s)"))
return return
} }
case err != nil: case err != nil:
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws")) render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws"))
return return
} }
@ -549,11 +551,11 @@ func isPostAsGet(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
payload, err := payloadFromContext(r.Context()) payload, err := payloadFromContext(r.Context())
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
if !payload.isPostAsGet { if !payload.isPostAsGet {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "expected POST-as-GET")) render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "expected POST-as-GET"))
return return
} }
next(w, r) next(w, r)

@ -15,6 +15,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/google/uuid"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/acme"
tassert "github.com/stretchr/testify/assert" tassert "github.com/stretchr/testify/assert"
@ -831,8 +832,37 @@ func TestHandler_lookupJWK(t *testing.T) {
}, },
statusCode: http.StatusUnauthorized, statusCode: http.StatusUnauthorized,
err: acme.NewError(acme.ErrorUnauthorizedType, err: acme.NewError(acme.ErrorUnauthorizedType,
"account provisioner does not match requested provisioner; account provisioner = %s, reqested provisioner = %s", "account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
prov.GetName(), "other"), "other", prov.GetName()),
}
},
"fail/account-with-location-prefix/bad-provisioner-id": func(t *testing.T) test {
p := newProvWithID()
acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerID: uuid.NewString()}
ctx := acme.NewProvisionerContext(context.Background(), p)
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)
return test{
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
db: &acme.MockDB{
MockGetAccount: func(ctx context.Context, id string) (*acme.Account, error) {
assert.Equals(t, id, accID)
return acc, nil
},
},
ctx: ctx,
next: func(w http.ResponseWriter, r *http.Request) {
_acc, err := accountFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _acc, acc)
_jwk, err := jwkFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _jwk, jwk)
w.Write(testBody)
},
statusCode: http.StatusUnauthorized,
err: acme.NewError(acme.ErrorUnauthorizedType,
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
acc.ProvisionerID, p.GetID()),
} }
}, },
"ok/account-with-location-prefix": func(t *testing.T) test { "ok/account-with-location-prefix": func(t *testing.T) test {
@ -885,6 +915,32 @@ func TestHandler_lookupJWK(t *testing.T) {
statusCode: 200, statusCode: 200,
} }
}, },
"ok/account-with-provisioner-id": func(t *testing.T) test {
p := newProvWithID()
acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerID: p.GetID()}
ctx := acme.NewProvisionerContext(context.Background(), p)
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)
return test{
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
db: &acme.MockDB{
MockGetAccount: func(ctx context.Context, id string) (*acme.Account, error) {
assert.Equals(t, id, accID)
return acc, nil
},
},
ctx: ctx,
next: func(w http.ResponseWriter, r *http.Request) {
_acc, err := accountFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _acc, acc)
_jwk, err := jwkFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _jwk, jwk)
w.Write(testBody)
},
statusCode: 200,
}
},
} }
for name, run := range tests { for name, run := range tests {
tc := run(t) tc := run(t)

@ -99,29 +99,29 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx) acc, err := accountFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
prov, err := provisionerFromContext(ctx) prov, err := provisionerFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
payload, err := payloadFromContext(ctx) payload, err := payloadFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
var nor NewOrderRequest var nor NewOrderRequest
if err := json.Unmarshal(payload.value, &nor); err != nil { if err := json.Unmarshal(payload.value, &nor); err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal new-order request payload")) "failed to unmarshal new-order request payload"))
return return
} }
if err := nor.Validate(); err != nil { if err := nor.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -130,39 +130,39 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
acmeProv, err := acmeProvisionerFromContext(ctx) acmeProv, err := acmeProvisionerFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
var eak *acme.ExternalAccountKey var eak *acme.ExternalAccountKey
if acmeProv.RequireEAB { if acmeProv.RequireEAB {
if eak, err = db.GetExternalAccountKeyByAccountID(ctx, prov.GetID(), acc.ID); err != nil { if eak, err = db.GetExternalAccountKeyByAccountID(ctx, prov.GetID(), acc.ID); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving external account binding key")) render.Error(w, r, acme.WrapErrorISE(err, "error retrieving external account binding key"))
return return
} }
} }
acmePolicy, err := newACMEPolicyEngine(eak) acmePolicy, err := newACMEPolicyEngine(eak)
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error creating ACME policy engine")) render.Error(w, r, acme.WrapErrorISE(err, "error creating ACME policy engine"))
return return
} }
for _, identifier := range nor.Identifiers { for _, identifier := range nor.Identifiers {
// evaluate the ACME account level policy // evaluate the ACME account level policy
if err = isIdentifierAllowed(acmePolicy, identifier); err != nil { if err = isIdentifierAllowed(acmePolicy, identifier); err != nil {
render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized")) render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
return return
} }
// evaluate the provisioner level policy // evaluate the provisioner level policy
orderIdentifier := provisioner.ACMEIdentifier{Type: provisioner.ACMEIdentifierType(identifier.Type), Value: identifier.Value} orderIdentifier := provisioner.ACMEIdentifier{Type: provisioner.ACMEIdentifierType(identifier.Type), Value: identifier.Value}
if err = prov.AuthorizeOrderIdentifier(ctx, orderIdentifier); err != nil { if err = prov.AuthorizeOrderIdentifier(ctx, orderIdentifier); err != nil {
render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized")) render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
return return
} }
// evaluate the authority level policy // evaluate the authority level policy
if err = ca.AreSANsAllowed(ctx, []string{identifier.Value}); err != nil { if err = ca.AreSANsAllowed(ctx, []string{identifier.Value}); err != nil {
render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized")) render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
return return
} }
} }
@ -188,7 +188,7 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
Status: acme.StatusPending, Status: acme.StatusPending,
} }
if err := newAuthorization(ctx, az); err != nil { if err := newAuthorization(ctx, az); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
o.AuthorizationIDs[i] = az.ID o.AuthorizationIDs[i] = az.ID
@ -207,14 +207,14 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
} }
if err := db.CreateOrder(ctx, o); err != nil { if err := db.CreateOrder(ctx, o); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error creating order")) render.Error(w, r, acme.WrapErrorISE(err, "error creating order"))
return return
} }
linker.LinkOrder(ctx, o) linker.LinkOrder(ctx, o)
w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID)) w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID))
render.JSONStatus(w, o, http.StatusCreated) render.JSONStatus(w, r, o, http.StatusCreated)
} }
func isIdentifierAllowed(acmePolicy policy.X509Policy, identifier acme.Identifier) error { func isIdentifierAllowed(acmePolicy policy.X509Policy, identifier acme.Identifier) error {
@ -226,6 +226,7 @@ func isIdentifierAllowed(acmePolicy policy.X509Policy, identifier acme.Identifie
func newACMEPolicyEngine(eak *acme.ExternalAccountKey) (policy.X509Policy, error) { func newACMEPolicyEngine(eak *acme.ExternalAccountKey) (policy.X509Policy, error) {
if eak == nil { if eak == nil {
//nolint:nilnil,nolintlint // expected values
return nil, nil return nil, nil
} }
return policy.NewX509PolicyEngine(eak.Policy) return policy.NewX509PolicyEngine(eak.Policy)
@ -288,39 +289,39 @@ func GetOrder(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx) acc, err := accountFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
prov, err := provisionerFromContext(ctx) prov, err := provisionerFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
o, err := db.GetOrder(ctx, chi.URLParam(r, "ordID")) o, err := db.GetOrder(ctx, chi.URLParam(r, "ordID"))
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving order")) render.Error(w, r, acme.WrapErrorISE(err, "error retrieving order"))
return return
} }
if acc.ID != o.AccountID { if acc.ID != o.AccountID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own order '%s'", acc.ID, o.ID)) "account '%s' does not own order '%s'", acc.ID, o.ID))
return return
} }
if prov.GetID() != o.ProvisionerID { if prov.GetID() != o.ProvisionerID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"provisioner '%s' does not own order '%s'", prov.GetID(), o.ID)) "provisioner '%s' does not own order '%s'", prov.GetID(), o.ID))
return return
} }
if err = o.UpdateStatus(ctx, db); err != nil { if err = o.UpdateStatus(ctx, db); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error updating order status")) render.Error(w, r, acme.WrapErrorISE(err, "error updating order status"))
return return
} }
linker.LinkOrder(ctx, o) linker.LinkOrder(ctx, o)
w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID)) w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID))
render.JSON(w, o) render.JSON(w, r, o)
} }
// FinalizeOrder attempts to finalize an order and create a certificate. // FinalizeOrder attempts to finalize an order and create a certificate.
@ -331,56 +332,56 @@ func FinalizeOrder(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx) acc, err := accountFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
prov, err := provisionerFromContext(ctx) prov, err := provisionerFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
payload, err := payloadFromContext(ctx) payload, err := payloadFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
var fr FinalizeRequest var fr FinalizeRequest
if err := json.Unmarshal(payload.value, &fr); err != nil { if err := json.Unmarshal(payload.value, &fr); err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal finalize-order request payload")) "failed to unmarshal finalize-order request payload"))
return return
} }
if err := fr.Validate(); err != nil { if err := fr.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
o, err := db.GetOrder(ctx, chi.URLParam(r, "ordID")) o, err := db.GetOrder(ctx, chi.URLParam(r, "ordID"))
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving order")) render.Error(w, r, acme.WrapErrorISE(err, "error retrieving order"))
return return
} }
if acc.ID != o.AccountID { if acc.ID != o.AccountID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own order '%s'", acc.ID, o.ID)) "account '%s' does not own order '%s'", acc.ID, o.ID))
return return
} }
if prov.GetID() != o.ProvisionerID { if prov.GetID() != o.ProvisionerID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"provisioner '%s' does not own order '%s'", prov.GetID(), o.ID)) "provisioner '%s' does not own order '%s'", prov.GetID(), o.ID))
return return
} }
ca := mustAuthority(ctx) ca := mustAuthority(ctx)
if err = o.Finalize(ctx, db, fr.csr, ca, prov); err != nil { if err = o.Finalize(ctx, db, fr.csr, ca, prov); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error finalizing order")) render.Error(w, r, acme.WrapErrorISE(err, "error finalizing order"))
return return
} }
linker.LinkOrder(ctx, o) linker.LinkOrder(ctx, o)
w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID)) w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID))
render.JSON(w, o) render.JSON(w, r, o)
} }
// challengeTypes determines the types of challenges that should be used // challengeTypes determines the types of challenges that should be used

@ -33,65 +33,65 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
jws, err := jwsFromContext(ctx) jws, err := jwsFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
prov, err := provisionerFromContext(ctx) prov, err := provisionerFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
payload, err := payloadFromContext(ctx) payload, err := payloadFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
var p revokePayload var p revokePayload
err = json.Unmarshal(payload.value, &p) err = json.Unmarshal(payload.value, &p)
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error unmarshaling payload")) render.Error(w, r, acme.WrapErrorISE(err, "error unmarshaling payload"))
return return
} }
certBytes, err := base64.RawURLEncoding.DecodeString(p.Certificate) certBytes, err := base64.RawURLEncoding.DecodeString(p.Certificate)
if err != nil { if err != nil {
// in this case the most likely cause is a client that didn't properly encode the certificate // in this case the most likely cause is a client that didn't properly encode the certificate
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error base64url decoding payload certificate property")) render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error base64url decoding payload certificate property"))
return return
} }
certToBeRevoked, err := x509.ParseCertificate(certBytes) certToBeRevoked, err := x509.ParseCertificate(certBytes)
if err != nil { if err != nil {
// in this case a client may have encoded something different than a certificate // in this case a client may have encoded something different than a certificate
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error parsing certificate")) render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error parsing certificate"))
return return
} }
serial := certToBeRevoked.SerialNumber.String() serial := certToBeRevoked.SerialNumber.String()
dbCert, err := db.GetCertificateBySerial(ctx, serial) dbCert, err := db.GetCertificateBySerial(ctx, serial)
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving certificate by serial")) render.Error(w, r, acme.WrapErrorISE(err, "error retrieving certificate by serial"))
return return
} }
if !bytes.Equal(dbCert.Leaf.Raw, certToBeRevoked.Raw) { if !bytes.Equal(dbCert.Leaf.Raw, certToBeRevoked.Raw) {
// this should never happen // this should never happen
render.Error(w, acme.NewErrorISE("certificate raw bytes are not equal")) render.Error(w, r, acme.NewErrorISE("certificate raw bytes are not equal"))
return return
} }
if shouldCheckAccountFrom(jws) { if shouldCheckAccountFrom(jws) {
account, err := accountFromContext(ctx) account, err := accountFromContext(ctx)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
acmeErr := isAccountAuthorized(ctx, dbCert, certToBeRevoked, account) acmeErr := isAccountAuthorized(ctx, dbCert, certToBeRevoked, account)
if acmeErr != nil { if acmeErr != nil {
render.Error(w, acmeErr) render.Error(w, r, acmeErr)
return return
} }
} else { } else {
@ -100,7 +100,7 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
_, err := jws.Verify(certToBeRevoked.PublicKey) _, err := jws.Verify(certToBeRevoked.PublicKey)
if err != nil { if err != nil {
// TODO(hs): possible to determine an error vs. unauthorized and thus provide an ISE vs. Unauthorized? // TODO(hs): possible to determine an error vs. unauthorized and thus provide an ISE vs. Unauthorized?
render.Error(w, wrapUnauthorizedError(certToBeRevoked, nil, "verification of jws using certificate public key failed", err)) render.Error(w, r, wrapUnauthorizedError(certToBeRevoked, nil, "verification of jws using certificate public key failed", err))
return return
} }
} }
@ -108,19 +108,19 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
ca := mustAuthority(ctx) ca := mustAuthority(ctx)
hasBeenRevokedBefore, err := ca.IsRevoked(serial) hasBeenRevokedBefore, err := ca.IsRevoked(serial)
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving revocation status of certificate")) render.Error(w, r, acme.WrapErrorISE(err, "error retrieving revocation status of certificate"))
return return
} }
if hasBeenRevokedBefore { if hasBeenRevokedBefore {
render.Error(w, acme.NewError(acme.ErrorAlreadyRevokedType, "certificate was already revoked")) render.Error(w, r, acme.NewError(acme.ErrorAlreadyRevokedType, "certificate was already revoked"))
return return
} }
reasonCode := p.ReasonCode reasonCode := p.ReasonCode
acmeErr := validateReasonCode(reasonCode) acmeErr := validateReasonCode(reasonCode)
if acmeErr != nil { if acmeErr != nil {
render.Error(w, acmeErr) render.Error(w, r, acmeErr)
return return
} }
@ -128,14 +128,14 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
ctx = provisioner.NewContextWithMethod(ctx, provisioner.RevokeMethod) ctx = provisioner.NewContextWithMethod(ctx, provisioner.RevokeMethod)
err = prov.AuthorizeRevoke(ctx, "") err = prov.AuthorizeRevoke(ctx, "")
if err != nil { if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error authorizing revocation on provisioner")) render.Error(w, r, acme.WrapErrorISE(err, "error authorizing revocation on provisioner"))
return return
} }
options := revokeOptions(serial, certToBeRevoked, reasonCode) options := revokeOptions(serial, certToBeRevoked, reasonCode)
err = ca.Revoke(ctx, options) err = ca.Revoke(ctx, options)
if err != nil { if err != nil {
render.Error(w, wrapRevokeErr(err)) render.Error(w, r, wrapRevokeErr(err))
return return
} }

@ -281,7 +281,7 @@ type mockCA struct {
MockAreSANsallowed func(ctx context.Context, sans []string) error MockAreSANsallowed func(ctx context.Context, sans []string) error
} }
func (m *mockCA) Sign(*x509.CertificateRequest, provisioner.SignOptions, ...provisioner.SignOption) ([]*x509.Certificate, error) { func (m *mockCA) SignWithContext(context.Context, *x509.CertificateRequest, provisioner.SignOptions, ...provisioner.SignOption) ([]*x509.Certificate, error) {
return nil, nil return nil, nil
} }

@ -726,7 +726,7 @@ var (
oidTCGKpAIKCertificate = asn1.ObjectIdentifier{2, 23, 133, 8, 3} oidTCGKpAIKCertificate = asn1.ObjectIdentifier{2, 23, 133, 8, 3}
) )
// validateAKCertifiate validates the X.509 AK certificate to be // validateAKCertificate validates the X.509 AK certificate to be
// in accordance with the required properties. The requirements come from: // in accordance with the required properties. The requirements come from:
// https://www.w3.org/TR/webauthn-2/#sctn-tpm-cert-requirements. // https://www.w3.org/TR/webauthn-2/#sctn-tpm-cert-requirements.
// //
@ -735,7 +735,7 @@ var (
// - The Subject Alternative Name extension MUST be set as defined // - The Subject Alternative Name extension MUST be set as defined
// in [TPMv2-EK-Profile] section 3.2.9. // in [TPMv2-EK-Profile] section 3.2.9.
// - The Extended Key Usage extension MUST contain the OID 2.23.133.8.3 // - The Extended Key Usage extension MUST contain the OID 2.23.133.8.3
// ("joint-iso-itu-t(2) internationalorganizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)"). // ("joint-iso-itu-t(2) international-organizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)").
// - The Basic Constraints extension MUST have the CA component set to false. // - The Basic Constraints extension MUST have the CA component set to false.
// - An Authority Information Access (AIA) extension with entry id-ad-ocsp // - An Authority Information Access (AIA) extension with entry id-ad-ocsp
// and a CRL Distribution Point extension [RFC5280] are both OPTIONAL as // and a CRL Distribution Point extension [RFC5280] are both OPTIONAL as

@ -21,7 +21,7 @@ var clock Clock
// CertificateAuthority is the interface implemented by a CA authority. // CertificateAuthority is the interface implemented by a CA authority.
type CertificateAuthority interface { type CertificateAuthority interface {
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
AreSANsAllowed(ctx context.Context, sans []string) error AreSANsAllowed(ctx context.Context, sans []string) error
IsRevoked(sn string) (bool, error) IsRevoked(sn string) (bool, error)
Revoke(context.Context, *authority.RevokeOptions) error Revoke(context.Context, *authority.RevokeOptions) error
@ -130,7 +130,7 @@ func (m *MockProvisioner) GetName() string {
return m.Mret1.(string) return m.Mret1.(string)
} }
// AuthorizeOrderIdentifiers mock // AuthorizeOrderIdentifier mock
func (m *MockProvisioner) AuthorizeOrderIdentifier(ctx context.Context, identifier provisioner.ACMEIdentifier) error { func (m *MockProvisioner) AuthorizeOrderIdentifier(ctx context.Context, identifier provisioner.ACMEIdentifier) error {
if m.MauthorizeOrderIdentifier != nil { if m.MauthorizeOrderIdentifier != nil {
return m.MauthorizeOrderIdentifier(ctx, identifier) return m.MauthorizeOrderIdentifier(ctx, identifier)

@ -2,6 +2,7 @@ package acme
import ( import (
"context" "context"
"database/sql"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -15,7 +16,7 @@ var ErrNotFound = errors.New("not found")
// IsErrNotFound returns true if the error is a "not found" error. Returns false // IsErrNotFound returns true if the error is a "not found" error. Returns false
// otherwise. // otherwise.
func IsErrNotFound(err error) bool { func IsErrNotFound(err error) bool {
return errors.Is(err, ErrNotFound) return errors.Is(err, ErrNotFound) || errors.Is(err, sql.ErrNoRows)
} }
// DB is the DB interface expected by the step-ca ACME API. // DB is the DB interface expected by the step-ca ACME API.

@ -18,6 +18,7 @@ type dbAccount struct {
Contact []string `json:"contact,omitempty"` Contact []string `json:"contact,omitempty"`
Status acme.Status `json:"status"` Status acme.Status `json:"status"`
LocationPrefix string `json:"locationPrefix"` LocationPrefix string `json:"locationPrefix"`
ProvisionerID string `json:"provisionerID,omitempty"`
ProvisionerName string `json:"provisionerName"` ProvisionerName string `json:"provisionerName"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
DeactivatedAt time.Time `json:"deactivatedAt"` DeactivatedAt time.Time `json:"deactivatedAt"`
@ -69,6 +70,7 @@ func (db *DB) GetAccount(ctx context.Context, id string) (*acme.Account, error)
Key: dbacc.Key, Key: dbacc.Key,
ID: dbacc.ID, ID: dbacc.ID,
LocationPrefix: dbacc.LocationPrefix, LocationPrefix: dbacc.LocationPrefix,
ProvisionerID: dbacc.ProvisionerID,
ProvisionerName: dbacc.ProvisionerName, ProvisionerName: dbacc.ProvisionerName,
}, nil }, nil
} }
@ -97,6 +99,7 @@ func (db *DB) CreateAccount(ctx context.Context, acc *acme.Account) error {
Status: acc.Status, Status: acc.Status,
CreatedAt: clock.Now(), CreatedAt: clock.Now(),
LocationPrefix: acc.LocationPrefix, LocationPrefix: acc.LocationPrefix,
ProvisionerID: acc.ProvisionerID,
ProvisionerName: acc.ProvisionerName, ProvisionerName: acc.ProvisionerName,
} }

@ -68,12 +68,14 @@ func TestDB_getDBAccount(t *testing.T) {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err) assert.FatalError(t, err)
dbacc := &dbAccount{ dbacc := &dbAccount{
ID: accID, ID: accID,
Status: acme.StatusDeactivated, Status: acme.StatusDeactivated,
CreatedAt: now, CreatedAt: now,
DeactivatedAt: now, DeactivatedAt: now,
Contact: []string{"foo", "bar"}, Contact: []string{"foo", "bar"},
Key: jwk, Key: jwk,
ProvisionerID: "73d2c0f1-9753-448b-9b48-bf00fe434681",
ProvisionerName: "acme",
} }
b, err := json.Marshal(dbacc) b, err := json.Marshal(dbacc)
assert.FatalError(t, err) assert.FatalError(t, err)

@ -0,0 +1,32 @@
package acme
import (
"database/sql"
"errors"
"fmt"
"testing"
)
func TestIsErrNotFound(t *testing.T) {
type args struct {
err error
}
tests := []struct {
name string
args args
want bool
}{
{"true ErrNotFound", args{ErrNotFound}, true},
{"true sql.ErrNoRows", args{sql.ErrNoRows}, true},
{"true wrapped ErrNotFound", args{fmt.Errorf("something failed: %w", ErrNotFound)}, true},
{"true wrapped sql.ErrNoRows", args{fmt.Errorf("something failed: %w", sql.ErrNoRows)}, true},
{"false other", args{errors.New("not found")}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsErrNotFound(tt.args.err); got != tt.want {
t.Errorf("IsErrNotFound() = %v, want %v", got, tt.want)
}
})
}
}

@ -424,7 +424,7 @@ func (e *Error) ToLog() (interface{}, error) {
} }
// Render implements render.RenderableError for Error. // Render implements render.RenderableError for Error.
func (e *Error) Render(w http.ResponseWriter) { func (e *Error) Render(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/problem+json") w.Header().Set("Content-Type", "application/problem+json")
render.JSONStatus(w, e, e.StatusCode()) render.JSONStatus(w, r, e, e.StatusCode())
} }

@ -186,19 +186,19 @@ func (l *linker) Middleware(next http.Handler) http.Handler {
nameEscaped := chi.URLParam(r, "provisionerID") nameEscaped := chi.URLParam(r, "provisionerID")
name, err := url.PathUnescape(nameEscaped) name, err := url.PathUnescape(nameEscaped)
if err != nil { if err != nil {
render.Error(w, WrapErrorISE(err, "error url unescaping provisioner name '%s'", nameEscaped)) render.Error(w, r, WrapErrorISE(err, "error url unescaping provisioner name '%s'", nameEscaped))
return return
} }
p, err := authority.MustFromContext(ctx).LoadProvisionerByName(name) p, err := authority.MustFromContext(ctx).LoadProvisionerByName(name)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
acmeProv, ok := p.(*provisioner.ACME) acmeProv, ok := p.(*provisioner.ACME)
if !ok { if !ok {
render.Error(w, NewError(ErrorAccountDoesNotExistType, "provisioner must be of type ACME")) render.Error(w, r, NewError(ErrorAccountDoesNotExistType, "provisioner must be of type ACME"))
return return
} }

@ -127,7 +127,7 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
return nil return nil
} }
// getKeyFingerprint returns a fingerprint from the list of authorizations. This // getAuthorizationFingerprint returns a fingerprint from the list of authorizations. This
// fingerprint is used on the device-attest-01 flow to verify the attestation // fingerprint is used on the device-attest-01 flow to verify the attestation
// certificate public key with the CSR public key. // certificate public key with the CSR public key.
// //
@ -263,7 +263,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
signOps = append(signOps, extraOptions...) signOps = append(signOps, extraOptions...)
// Sign a new certificate. // Sign a new certificate.
certChain, err := auth.Sign(csr, provisioner.SignOptions{ certChain, err := auth.SignWithContext(ctx, csr, provisioner.SignOptions{
NotBefore: provisioner.NewTimeDuration(o.NotBefore), NotBefore: provisioner.NewTimeDuration(o.NotBefore),
NotAfter: provisioner.NewTimeDuration(o.NotAfter), NotAfter: provisioner.NewTimeDuration(o.NotAfter),
}, signOps...) }, signOps...)

@ -271,16 +271,16 @@ func TestOrder_UpdateStatus(t *testing.T) {
} }
type mockSignAuth struct { type mockSignAuth struct {
sign func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) signWithContext func(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
areSANsAllowed func(ctx context.Context, sans []string) error areSANsAllowed func(ctx context.Context, sans []string) error
loadProvisionerByName func(string) (provisioner.Interface, error) loadProvisionerByName func(string) (provisioner.Interface, error)
ret1, ret2 interface{} ret1, ret2 interface{}
err error err error
} }
func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { func (m *mockSignAuth) SignWithContext(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
if m.sign != nil { if m.signWithContext != nil {
return m.sign(csr, signOpts, extraOpts...) return m.signWithContext(ctx, csr, signOpts, extraOpts...)
} else if m.err != nil { } else if m.err != nil {
return nil, m.err return nil, m.err
} }
@ -578,7 +578,7 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
ca: &mockSignAuth{ ca: &mockSignAuth{
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr) assert.Equals(t, _csr, csr)
return nil, errors.New("force") return nil, errors.New("force")
}, },
@ -628,7 +628,7 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
ca: &mockSignAuth{ ca: &mockSignAuth{
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr) assert.Equals(t, _csr, csr)
return []*x509.Certificate{foo, bar, baz}, nil return []*x509.Certificate{foo, bar, baz}, nil
}, },
@ -685,7 +685,7 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
ca: &mockSignAuth{ ca: &mockSignAuth{
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr) assert.Equals(t, _csr, csr)
return []*x509.Certificate{foo, bar, baz}, nil return []*x509.Certificate{foo, bar, baz}, nil
}, },
@ -770,7 +770,7 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
ca: &mockSignAuth{ ca: &mockSignAuth{
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr) assert.Equals(t, _csr, csr)
return []*x509.Certificate{leaf, inter, root}, nil return []*x509.Certificate{leaf, inter, root}, nil
}, },
@ -863,7 +863,7 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
ca: &mockSignAuth{ ca: &mockSignAuth{
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr) assert.Equals(t, _csr, csr)
return []*x509.Certificate{leaf, inter, root}, nil return []*x509.Certificate{leaf, inter, root}, nil
}, },
@ -973,7 +973,7 @@ func TestOrder_Finalize(t *testing.T) {
// using the mocking functions as a wrapper for actual test helpers generated per test case or per // using the mocking functions as a wrapper for actual test helpers generated per test case or per
// function that's tested. // function that's tested.
ca: &mockSignAuth{ ca: &mockSignAuth{
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr) assert.Equals(t, _csr, csr)
return []*x509.Certificate{leaf, inter, root}, nil return []*x509.Certificate{leaf, inter, root}, nil
}, },
@ -1044,7 +1044,7 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
ca: &mockSignAuth{ ca: &mockSignAuth{
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr) assert.Equals(t, _csr, csr)
return []*x509.Certificate{foo, bar, baz}, nil return []*x509.Certificate{foo, bar, baz}, nil
}, },
@ -1108,7 +1108,7 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
ca: &mockSignAuth{ ca: &mockSignAuth{
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr) assert.Equals(t, _csr, csr)
return []*x509.Certificate{foo, bar, baz}, nil return []*x509.Certificate{foo, bar, baz}, nil
}, },
@ -1175,7 +1175,7 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
ca: &mockSignAuth{ ca: &mockSignAuth{
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr) assert.Equals(t, _csr, csr)
return []*x509.Certificate{foo, bar, baz}, nil return []*x509.Certificate{foo, bar, baz}, nil
}, },

@ -42,7 +42,7 @@ type Authority interface {
AuthorizeRenewToken(ctx context.Context, ott string) (*x509.Certificate, error) AuthorizeRenewToken(ctx context.Context, ott string) (*x509.Certificate, error)
GetTLSOptions() *config.TLSOptions GetTLSOptions() *config.TLSOptions
Root(shasum string) (*x509.Certificate, error) Root(shasum string) (*x509.Certificate, error)
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
Renew(peer *x509.Certificate) ([]*x509.Certificate, error) Renew(peer *x509.Certificate) ([]*x509.Certificate, error)
RenewContext(ctx context.Context, peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) RenewContext(ctx context.Context, peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
@ -54,7 +54,7 @@ type Authority interface {
GetRoots() ([]*x509.Certificate, error) GetRoots() ([]*x509.Certificate, error)
GetFederation() ([]*x509.Certificate, error) GetFederation() ([]*x509.Certificate, error)
Version() authority.Version Version() authority.Version
GetCertificateRevocationList() ([]byte, error) GetCertificateRevocationList() (*authority.CertificateRevocationListInfo, error)
} }
// mustAuthority will be replaced on unit tests. // mustAuthority will be replaced on unit tests.
@ -353,15 +353,15 @@ func Route(r Router) {
// Version is an HTTP handler that returns the version of the server. // Version is an HTTP handler that returns the version of the server.
func Version(w http.ResponseWriter, r *http.Request) { func Version(w http.ResponseWriter, r *http.Request) {
v := mustAuthority(r.Context()).Version() v := mustAuthority(r.Context()).Version()
render.JSON(w, VersionResponse{ render.JSON(w, r, VersionResponse{
Version: v.Version, Version: v.Version,
RequireClientAuthentication: v.RequireClientAuthentication, RequireClientAuthentication: v.RequireClientAuthentication,
}) })
} }
// Health is an HTTP handler that returns the status of the server. // Health is an HTTP handler that returns the status of the server.
func Health(w http.ResponseWriter, _ *http.Request) { func Health(w http.ResponseWriter, r *http.Request) {
render.JSON(w, HealthResponse{Status: "ok"}) render.JSON(w, r, HealthResponse{Status: "ok"})
} }
// Root is an HTTP handler that using the SHA256 from the URL, returns the root // Root is an HTTP handler that using the SHA256 from the URL, returns the root
@ -372,11 +372,11 @@ func Root(w http.ResponseWriter, r *http.Request) {
// Load root certificate with the // Load root certificate with the
cert, err := mustAuthority(r.Context()).Root(sum) cert, err := mustAuthority(r.Context()).Root(sum)
if err != nil { if err != nil {
render.Error(w, errs.Wrapf(http.StatusNotFound, err, "%s was not found", r.RequestURI)) render.Error(w, r, errs.Wrapf(http.StatusNotFound, err, "%s was not found", r.RequestURI))
return return
} }
render.JSON(w, &RootResponse{RootPEM: Certificate{cert}}) render.JSON(w, r, &RootResponse{RootPEM: Certificate{cert}})
} }
func certChainToPEM(certChain []*x509.Certificate) []Certificate { func certChainToPEM(certChain []*x509.Certificate) []Certificate {
@ -391,17 +391,17 @@ func certChainToPEM(certChain []*x509.Certificate) []Certificate {
func Provisioners(w http.ResponseWriter, r *http.Request) { func Provisioners(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := ParseCursor(r) cursor, limit, err := ParseCursor(r)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
p, next, err := mustAuthority(r.Context()).GetProvisioners(cursor, limit) p, next, err := mustAuthority(r.Context()).GetProvisioners(cursor, limit)
if err != nil { if err != nil {
render.Error(w, errs.InternalServerErr(err)) render.Error(w, r, errs.InternalServerErr(err))
return return
} }
render.JSON(w, &ProvisionersResponse{ render.JSON(w, r, &ProvisionersResponse{
Provisioners: p, Provisioners: p,
NextCursor: next, NextCursor: next,
}) })
@ -412,18 +412,18 @@ func ProvisionerKey(w http.ResponseWriter, r *http.Request) {
kid := chi.URLParam(r, "kid") kid := chi.URLParam(r, "kid")
key, err := mustAuthority(r.Context()).GetEncryptedKey(kid) key, err := mustAuthority(r.Context()).GetEncryptedKey(kid)
if err != nil { if err != nil {
render.Error(w, errs.NotFoundErr(err)) render.Error(w, r, errs.NotFoundErr(err))
return return
} }
render.JSON(w, &ProvisionerKeyResponse{key}) render.JSON(w, r, &ProvisionerKeyResponse{key})
} }
// Roots returns all the root certificates for the CA. // Roots returns all the root certificates for the CA.
func Roots(w http.ResponseWriter, r *http.Request) { func Roots(w http.ResponseWriter, r *http.Request) {
roots, err := mustAuthority(r.Context()).GetRoots() roots, err := mustAuthority(r.Context()).GetRoots()
if err != nil { if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error getting roots")) render.Error(w, r, errs.ForbiddenErr(err, "error getting roots"))
return return
} }
@ -432,7 +432,7 @@ func Roots(w http.ResponseWriter, r *http.Request) {
certs[i] = Certificate{roots[i]} certs[i] = Certificate{roots[i]}
} }
render.JSONStatus(w, &RootsResponse{ render.JSONStatus(w, r, &RootsResponse{
Certificates: certs, Certificates: certs,
}, http.StatusCreated) }, http.StatusCreated)
} }
@ -441,7 +441,7 @@ func Roots(w http.ResponseWriter, r *http.Request) {
func RootsPEM(w http.ResponseWriter, r *http.Request) { func RootsPEM(w http.ResponseWriter, r *http.Request) {
roots, err := mustAuthority(r.Context()).GetRoots() roots, err := mustAuthority(r.Context()).GetRoots()
if err != nil { if err != nil {
render.Error(w, errs.InternalServerErr(err)) render.Error(w, r, errs.InternalServerErr(err))
return return
} }
@ -454,7 +454,7 @@ func RootsPEM(w http.ResponseWriter, r *http.Request) {
}) })
if _, err := w.Write(block); err != nil { if _, err := w.Write(block); err != nil {
log.Error(w, err) log.Error(w, r, err)
return return
} }
} }
@ -464,7 +464,7 @@ func RootsPEM(w http.ResponseWriter, r *http.Request) {
func Federation(w http.ResponseWriter, r *http.Request) { func Federation(w http.ResponseWriter, r *http.Request) {
federated, err := mustAuthority(r.Context()).GetFederation() federated, err := mustAuthority(r.Context()).GetFederation()
if err != nil { if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error getting federated roots")) render.Error(w, r, errs.ForbiddenErr(err, "error getting federated roots"))
return return
} }
@ -473,7 +473,7 @@ func Federation(w http.ResponseWriter, r *http.Request) {
certs[i] = Certificate{federated[i]} certs[i] = Certificate{federated[i]}
} }
render.JSONStatus(w, &FederationResponse{ render.JSONStatus(w, r, &FederationResponse{
Certificates: certs, Certificates: certs,
}, http.StatusCreated) }, http.StatusCreated)
} }
@ -565,7 +565,7 @@ func LogSSHCertificate(w http.ResponseWriter, cert *ssh.Certificate) {
func ParseCursor(r *http.Request) (cursor string, limit int, err error) { func ParseCursor(r *http.Request) (cursor string, limit int, err error) {
q := r.URL.Query() q := r.URL.Query()
cursor = q.Get("cursor") cursor = q.Get("cursor")
if v := q.Get("limit"); len(v) > 0 { if v := q.Get("limit"); v != "" {
limit, err = strconv.Atoi(v) limit, err = strconv.Atoi(v)
if err != nil { if err != nil {
return "", 0, errs.BadRequestErr(err, "limit '%s' is not an integer", v) return "", 0, errs.BadRequestErr(err, "limit '%s' is not an integer", v)

@ -189,7 +189,7 @@ type mockAuthority struct {
authorizeRenewToken func(ctx context.Context, ott string) (*x509.Certificate, error) authorizeRenewToken func(ctx context.Context, ott string) (*x509.Certificate, error)
getTLSOptions func() *authority.TLSOptions getTLSOptions func() *authority.TLSOptions
root func(shasum string) (*x509.Certificate, error) root func(shasum string) (*x509.Certificate, error)
sign func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) signWithContext func(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
renew func(cert *x509.Certificate) ([]*x509.Certificate, error) renew func(cert *x509.Certificate) ([]*x509.Certificate, error)
rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
renewContext func(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) renewContext func(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
@ -200,7 +200,7 @@ type mockAuthority struct {
getEncryptedKey func(kid string) (string, error) getEncryptedKey func(kid string) (string, error)
getRoots func() ([]*x509.Certificate, error) getRoots func() ([]*x509.Certificate, error)
getFederation func() ([]*x509.Certificate, error) getFederation func() ([]*x509.Certificate, error)
getCRL func() ([]byte, error) getCRL func() (*authority.CertificateRevocationListInfo, error)
signSSH func(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) signSSH func(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
signSSHAddUser func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) signSSHAddUser func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
renewSSH func(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error) renewSSH func(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error)
@ -214,12 +214,12 @@ type mockAuthority struct {
version func() authority.Version version func() authority.Version
} }
func (m *mockAuthority) GetCertificateRevocationList() ([]byte, error) { func (m *mockAuthority) GetCertificateRevocationList() (*authority.CertificateRevocationListInfo, error) {
if m.getCRL != nil { if m.getCRL != nil {
return m.getCRL() return m.getCRL()
} }
return m.ret1.([]byte), m.err return m.ret1.(*authority.CertificateRevocationListInfo), m.err
} }
// TODO: remove once Authorize is deprecated. // TODO: remove once Authorize is deprecated.
@ -251,9 +251,9 @@ func (m *mockAuthority) Root(shasum string) (*x509.Certificate, error) {
return m.ret1.(*x509.Certificate), m.err return m.ret1.(*x509.Certificate), m.err
} }
func (m *mockAuthority) Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { func (m *mockAuthority) SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
if m.sign != nil { if m.signWithContext != nil {
return m.sign(cr, opts, signOpts...) return m.signWithContext(ctx, cr, opts, signOpts...)
} }
return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err
} }
@ -789,45 +789,6 @@ func (m *mockProvisioner) AuthorizeSSHRekey(ctx context.Context, token string) (
return m.ret1.(*ssh.Certificate), m.ret2.([]provisioner.SignOption), m.err return m.ret1.(*ssh.Certificate), m.ret2.([]provisioner.SignOption), m.err
} }
func Test_CRLGeneration(t *testing.T) {
tests := []struct {
name string
err error
statusCode int
expected []byte
}{
{"empty", nil, http.StatusOK, nil},
}
chiCtx := chi.NewRouteContext()
req := httptest.NewRequest("GET", "http://example.com/crl", http.NoBody)
req = req.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockMustAuthority(t, &mockAuthority{ret1: tt.expected, err: tt.err})
w := httptest.NewRecorder()
CRL(w, req)
res := w.Result()
if res.StatusCode != tt.statusCode {
t.Errorf("caHandler.CRL StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
}
body, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Errorf("caHandler.Root unexpected error = %v", err)
}
if tt.statusCode == 200 {
if !bytes.Equal(bytes.TrimSpace(body), tt.expected) {
t.Errorf("caHandler.Root CRL = %s, wants %s", body, tt.expected)
}
}
})
}
}
func Test_caHandler_Route(t *testing.T) { func Test_caHandler_Route(t *testing.T) {
type fields struct { type fields struct {
Authority Authority Authority Authority
@ -923,16 +884,12 @@ func Test_Sign(t *testing.T) {
CsrPEM: CertificateRequest{csr}, CsrPEM: CertificateRequest{csr},
OTT: "foobarzar", OTT: "foobarzar",
}) })
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
invalid, err := json.Marshal(SignRequest{ invalid, err := json.Marshal(SignRequest{
CsrPEM: CertificateRequest{csr}, CsrPEM: CertificateRequest{csr},
OTT: "", OTT: "",
}) })
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
expected1 := []byte(`{"crt":"` + strings.ReplaceAll(certPEM, "\n", `\n`) + `\n","ca":"` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n","certChain":["` + strings.ReplaceAll(certPEM, "\n", `\n`) + `\n","` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n"]}`) expected1 := []byte(`{"crt":"` + strings.ReplaceAll(certPEM, "\n", `\n`) + `\n","ca":"` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n","certChain":["` + strings.ReplaceAll(certPEM, "\n", `\n`) + `\n","` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n"]}`)
expected2 := []byte(`{"crt":"` + strings.ReplaceAll(stepCertPEM, "\n", `\n`) + `\n","ca":"` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n","certChain":["` + strings.ReplaceAll(stepCertPEM, "\n", `\n`) + `\n","` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n"]}`) expected2 := []byte(`{"crt":"` + strings.ReplaceAll(stepCertPEM, "\n", `\n`) + `\n","ca":"` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n","certChain":["` + strings.ReplaceAll(stepCertPEM, "\n", `\n`) + `\n","` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n"]}`)

@ -3,18 +3,32 @@ package api
import ( import (
"encoding/pem" "encoding/pem"
"net/http" "net/http"
"time"
"github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/errs"
) )
// CRL is an HTTP handler that returns the current CRL in DER or PEM format // CRL is an HTTP handler that returns the current CRL in DER or PEM format
func CRL(w http.ResponseWriter, r *http.Request) { func CRL(w http.ResponseWriter, r *http.Request) {
crlBytes, err := mustAuthority(r.Context()).GetCertificateRevocationList() crlInfo, err := mustAuthority(r.Context()).GetCertificateRevocationList()
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
if crlInfo == nil {
render.Error(w, r, errs.New(http.StatusNotFound, "no CRL available"))
return
}
expires := crlInfo.ExpiresAt
if expires.IsZero() {
expires = time.Now()
}
w.Header().Add("Expires", expires.Format(time.RFC1123))
_, formatAsPEM := r.URL.Query()["pem"] _, formatAsPEM := r.URL.Query()["pem"]
if formatAsPEM { if formatAsPEM {
w.Header().Add("Content-Type", "application/x-pem-file") w.Header().Add("Content-Type", "application/x-pem-file")
@ -22,11 +36,11 @@ func CRL(w http.ResponseWriter, r *http.Request) {
_ = pem.Encode(w, &pem.Block{ _ = pem.Encode(w, &pem.Block{
Type: "X509 CRL", Type: "X509 CRL",
Bytes: crlBytes, Bytes: crlInfo.Data,
}) })
} else { } else {
w.Header().Add("Content-Type", "application/pkix-crl") w.Header().Add("Content-Type", "application/pkix-crl")
w.Header().Add("Content-Disposition", "attachment; filename=\"crl.der\"") w.Header().Add("Content-Disposition", "attachment; filename=\"crl.der\"")
w.Write(crlBytes) w.Write(crlInfo.Data)
} }
} }

@ -0,0 +1,93 @@
package api
import (
"bytes"
"context"
"encoding/pem"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/go-chi/chi/v5"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/errs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_CRL(t *testing.T) {
data := []byte{1, 2, 3, 4}
pemData := pem.EncodeToMemory(&pem.Block{
Type: "X509 CRL",
Bytes: data,
})
pemData = bytes.TrimSpace(pemData)
emptyPEMData := pem.EncodeToMemory(&pem.Block{
Type: "X509 CRL",
Bytes: nil,
})
emptyPEMData = bytes.TrimSpace(emptyPEMData)
tests := []struct {
name string
url string
err error
statusCode int
crlInfo *authority.CertificateRevocationListInfo
expectedBody []byte
expectedHeaders http.Header
expectedErrorJSON string
}{
{"ok", "http://example.com/crl", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: data}, data, http.Header{"Content-Type": []string{"application/pkix-crl"}, "Content-Disposition": []string{`attachment; filename="crl.der"`}}, ""},
{"ok/pem", "http://example.com/crl?pem=true", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: data}, pemData, http.Header{"Content-Type": []string{"application/x-pem-file"}, "Content-Disposition": []string{`attachment; filename="crl.pem"`}}, ""},
{"ok/empty", "http://example.com/crl", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: nil}, nil, http.Header{"Content-Type": []string{"application/pkix-crl"}, "Content-Disposition": []string{`attachment; filename="crl.der"`}}, ""},
{"ok/empty-pem", "http://example.com/crl?pem=true", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: nil}, emptyPEMData, http.Header{"Content-Type": []string{"application/x-pem-file"}, "Content-Disposition": []string{`attachment; filename="crl.pem"`}}, ""},
{"fail/internal", "http://example.com/crl", errs.Wrap(http.StatusInternalServerError, errors.New("failure"), "authority.GetCertificateRevocationList"), http.StatusInternalServerError, nil, nil, http.Header{}, `{"status":500,"message":"The certificate authority encountered an Internal Server Error. Please see the certificate authority logs for more info."}`},
{"fail/nil", "http://example.com/crl", nil, http.StatusNotFound, nil, nil, http.Header{}, `{"status":404,"message":"no CRL available"}`},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockMustAuthority(t, &mockAuthority{ret1: tt.crlInfo, err: tt.err})
chiCtx := chi.NewRouteContext()
req := httptest.NewRequest("GET", tt.url, http.NoBody)
req = req.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx))
w := httptest.NewRecorder()
CRL(w, req)
res := w.Result()
assert.Equal(t, tt.statusCode, res.StatusCode)
body, err := io.ReadAll(res.Body)
res.Body.Close()
require.NoError(t, err)
if tt.statusCode >= 300 {
assert.JSONEq(t, tt.expectedErrorJSON, string(bytes.TrimSpace(body)))
return
}
// check expected header values
for _, h := range []string{"content-type", "content-disposition"} {
v := tt.expectedHeaders.Get(h)
require.NotEmpty(t, v)
actual := res.Header.Get(h)
assert.Equal(t, v, actual)
}
// check expires header value
assert.NotEmpty(t, res.Header.Get("expires"))
t1, err := time.Parse(time.RFC1123, res.Header.Get("expires"))
if assert.NoError(t, err) {
assert.False(t, t1.IsZero())
}
// check body contents
assert.Equal(t, tt.expectedBody, bytes.TrimSpace(body))
})
}
}

@ -2,6 +2,7 @@
package log package log
import ( import (
"context"
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
@ -9,6 +10,29 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type errorLoggerKey struct{}
// ErrorLogger is the function type used to log errors.
type ErrorLogger func(http.ResponseWriter, *http.Request, error)
func (fn ErrorLogger) call(w http.ResponseWriter, r *http.Request, err error) {
if fn == nil {
return
}
fn(w, r, err)
}
// WithErrorLogger returns a new context with the given error logger.
func WithErrorLogger(ctx context.Context, fn ErrorLogger) context.Context {
return context.WithValue(ctx, errorLoggerKey{}, fn)
}
// ErrorLoggerFromContext returns an error logger from the context.
func ErrorLoggerFromContext(ctx context.Context) (fn ErrorLogger) {
fn, _ = ctx.Value(errorLoggerKey{}).(ErrorLogger)
return
}
// StackTracedError is the set of errors implementing the StackTrace function. // StackTracedError is the set of errors implementing the StackTrace function.
// //
// Errors implementing this interface have their stack traces logged when passed // Errors implementing this interface have their stack traces logged when passed
@ -27,8 +51,10 @@ type fieldCarrier interface {
// Error adds to the response writer the given error if it implements // Error adds to the response writer the given error if it implements
// logging.ResponseLogger. If it does not implement it, then writes the error // logging.ResponseLogger. If it does not implement it, then writes the error
// using the log package. // using the log package.
func Error(rw http.ResponseWriter, err error) { func Error(w http.ResponseWriter, r *http.Request, err error) {
fc, ok := rw.(fieldCarrier) ErrorLoggerFromContext(r.Context()).call(w, r, err)
fc, ok := w.(fieldCarrier)
if !ok { if !ok {
return return
} }
@ -51,7 +77,7 @@ func Error(rw http.ResponseWriter, err error) {
// EnabledResponse log the response object if it implements the EnableLogger // EnabledResponse log the response object if it implements the EnableLogger
// interface. // interface.
func EnabledResponse(rw http.ResponseWriter, v any) { func EnabledResponse(rw http.ResponseWriter, r *http.Request, v any) {
type enableLogger interface { type enableLogger interface {
ToLog() (any, error) ToLog() (any, error)
} }
@ -59,7 +85,7 @@ func EnabledResponse(rw http.ResponseWriter, v any) {
if el, ok := v.(enableLogger); ok { if el, ok := v.(enableLogger); ok {
out, err := el.ToLog() out, err := el.ToLog()
if err != nil { if err != nil {
Error(rw, err) Error(rw, r, err)
return return
} }

@ -1,6 +1,9 @@
package log package log
import ( import (
"bytes"
"encoding/json"
"log/slog"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
@ -27,21 +30,34 @@ func (stackTracedError) StackTrace() pkgerrors.StackTrace {
} }
func TestError(t *testing.T) { func TestError(t *testing.T) {
var buf bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{}))
req := httptest.NewRequest("GET", "/test", http.NoBody)
reqWithLogger := req.WithContext(WithErrorLogger(req.Context(), func(w http.ResponseWriter, r *http.Request, err error) {
if err != nil {
logger.ErrorContext(r.Context(), "request failed", slog.Any("error", err))
}
}))
tests := []struct { tests := []struct {
name string name string
error error
rw http.ResponseWriter rw http.ResponseWriter
r *http.Request
isFieldCarrier bool isFieldCarrier bool
isSlogLogger bool
stepDebug bool stepDebug bool
expectStackTrace bool expectStackTrace bool
}{ }{
{"noLogger", nil, nil, false, false, false}, {"noLogger", nil, nil, req, false, false, false, false},
{"noError", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false}, {"noError", nil, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, false, false},
{"noErrorDebug", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false}, {"noErrorDebug", nil, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, false},
{"anError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false}, {"anError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, false, false},
{"anErrorDebug", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false}, {"anErrorDebug", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, false},
{"stackTracedError", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true}, {"stackTracedError", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, true},
{"stackTracedErrorDebug", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true}, {"stackTracedErrorDebug", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, true},
{"slogWithNoError", nil, logging.NewResponseLogger(httptest.NewRecorder()), reqWithLogger, true, true, false, false},
{"slogWithError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), reqWithLogger, true, true, false, false},
} }
for _, tt := range tests { for _, tt := range tests {
@ -52,27 +68,41 @@ func TestError(t *testing.T) {
t.Setenv("STEPDEBUG", "0") t.Setenv("STEPDEBUG", "0")
} }
Error(tt.rw, tt.error) Error(tt.rw, tt.r, tt.error)
// return early if test case doesn't use logger // return early if test case doesn't use logger
if !tt.isFieldCarrier { if !tt.isFieldCarrier && !tt.isSlogLogger {
return return
} }
fields := tt.rw.(logging.ResponseLogger).Fields() if tt.isFieldCarrier {
fields := tt.rw.(logging.ResponseLogger).Fields()
// expect the error field to be (not) set and to be the same error that was fed to Error // expect the error field to be (not) set and to be the same error that was fed to Error
if tt.error == nil { if tt.error == nil {
assert.Nil(t, fields["error"]) assert.Nil(t, fields["error"])
} else { } else {
assert.Same(t, tt.error, fields["error"]) assert.Same(t, tt.error, fields["error"])
}
// check if stack-trace is set when expected
if _, hasStackTrace := fields["stack-trace"]; tt.expectStackTrace && !hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] not set`)
} else if !tt.expectStackTrace && hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] was set`)
}
} }
// check if stack-trace is set when expected if tt.isSlogLogger {
if _, hasStackTrace := fields["stack-trace"]; tt.expectStackTrace && !hasStackTrace { b := buf.Bytes()
t.Error(`ResponseLogger["stack-trace"] not set`) if tt.error == nil {
} else if !tt.expectStackTrace && hasStackTrace { assert.Empty(t, b)
t.Error(`ResponseLogger["stack-trace"] was set`) } else if assert.NotEmpty(t, b) {
var m map[string]any
assert.NoError(t, json.Unmarshal(b, &m))
assert.Equal(t, tt.error.Error(), m["error"])
}
buf.Reset()
} }
}) })
} }

@ -97,7 +97,7 @@ func (s *SCEP) AuthorizeSSHSign(context.Context, string) ([]provisioner.SignOpti
return nil, errDummyImplementation return nil, errDummyImplementation
} }
// AuthorizeRevoke returns an unimplemented error. Provisioners should overwrite // AuthorizeSSHRevoke returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for revoking SSH Certificates. // this method if they will support authorizing tokens for revoking SSH Certificates.
func (s *SCEP) AuthorizeSSHRevoke(context.Context, string) error { func (s *SCEP) AuthorizeSSHRevoke(context.Context, string) error {
return errDummyImplementation return errDummyImplementation

@ -51,7 +51,7 @@ func (e badProtoJSONError) Error() string {
} }
// Render implements render.RenderableError for badProtoJSONError // Render implements render.RenderableError for badProtoJSONError
func (e badProtoJSONError) Render(w http.ResponseWriter) { func (e badProtoJSONError) Render(w http.ResponseWriter, r *http.Request) {
v := struct { v := struct {
Type string `json:"type"` Type string `json:"type"`
Detail string `json:"detail"` Detail string `json:"detail"`
@ -62,5 +62,5 @@ func (e badProtoJSONError) Render(w http.ResponseWriter) {
// trim the proto prefix for the message // trim the proto prefix for the message
Message: strings.TrimSpace(strings.TrimPrefix(e.Error(), "proto:")), Message: strings.TrimSpace(strings.TrimPrefix(e.Error(), "proto:")),
} }
render.JSONStatus(w, v, http.StatusBadRequest) render.JSONStatus(w, r, v, http.StatusBadRequest)
} }

@ -142,7 +142,8 @@ func Test_badProtoJSONError_Render(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
tt.e.Render(w) r := httptest.NewRequest("POST", "/test", http.NoBody)
tt.e.Render(w, r)
res := w.Result() res := w.Result()
defer res.Body.Close() defer res.Body.Close()

@ -29,25 +29,25 @@ func (s *RekeyRequest) Validate() error {
// Rekey is similar to renew except that the certificate will be renewed with new key from csr. // Rekey is similar to renew except that the certificate will be renewed with new key from csr.
func Rekey(w http.ResponseWriter, r *http.Request) { func Rekey(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 { if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
render.Error(w, errs.BadRequest("missing client certificate")) render.Error(w, r, errs.BadRequest("missing client certificate"))
return return
} }
var body RekeyRequest var body RekeyRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body")) render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return return
} }
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
a := mustAuthority(r.Context()) a := mustAuthority(r.Context())
certChain, err := a.Rekey(r.TLS.PeerCertificates[0], body.CsrPEM.CertificateRequest.PublicKey) certChain, err := a.Rekey(r.TLS.PeerCertificates[0], body.CsrPEM.CertificateRequest.PublicKey)
if err != nil { if err != nil {
render.Error(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey")) render.Error(w, r, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey"))
return return
} }
certChainPEM := certChainToPEM(certChain) certChainPEM := certChainToPEM(certChain)
@ -57,7 +57,7 @@ func Rekey(w http.ResponseWriter, r *http.Request) {
} }
LogCertificate(w, certChain[0]) LogCertificate(w, certChain[0])
render.JSONStatus(w, &SignResponse{ render.JSONStatus(w, r, &SignResponse{
ServerPEM: certChainPEM[0], ServerPEM: certChainPEM[0],
CaPEM: caPEM, CaPEM: caPEM,
CertChainPEM: certChainPEM, CertChainPEM: certChainPEM,

@ -13,8 +13,8 @@ import (
) )
// JSON is shorthand for JSONStatus(w, v, http.StatusOK). // JSON is shorthand for JSONStatus(w, v, http.StatusOK).
func JSON(w http.ResponseWriter, v interface{}) { func JSON(w http.ResponseWriter, r *http.Request, v interface{}) {
JSONStatus(w, v, http.StatusOK) JSONStatus(w, r, v, http.StatusOK)
} }
// JSONStatus marshals v into w. It additionally sets the status code of // JSONStatus marshals v into w. It additionally sets the status code of
@ -22,7 +22,7 @@ func JSON(w http.ResponseWriter, v interface{}) {
// //
// JSONStatus sets the Content-Type of w to application/json unless one is // JSONStatus sets the Content-Type of w to application/json unless one is
// specified. // specified.
func JSONStatus(w http.ResponseWriter, v interface{}, status int) { func JSONStatus(w http.ResponseWriter, r *http.Request, v interface{}, status int) {
setContentTypeUnlessPresent(w, "application/json") setContentTypeUnlessPresent(w, "application/json")
w.WriteHeader(status) w.WriteHeader(status)
@ -43,7 +43,7 @@ func JSONStatus(w http.ResponseWriter, v interface{}, status int) {
} }
} }
log.EnabledResponse(w, v) log.EnabledResponse(w, r, v)
} }
// ProtoJSON is shorthand for ProtoJSONStatus(w, m, http.StatusOK). // ProtoJSON is shorthand for ProtoJSONStatus(w, m, http.StatusOK).
@ -80,22 +80,22 @@ func setContentTypeUnlessPresent(w http.ResponseWriter, contentType string) {
type RenderableError interface { type RenderableError interface {
error error
Render(http.ResponseWriter) Render(http.ResponseWriter, *http.Request)
} }
// Error marshals the JSON representation of err to w. In case err implements // Error marshals the JSON representation of err to w. In case err implements
// RenderableError its own Render method will be called instead. // RenderableError its own Render method will be called instead.
func Error(w http.ResponseWriter, err error) { func Error(rw http.ResponseWriter, r *http.Request, err error) {
log.Error(w, err) log.Error(rw, r, err)
var r RenderableError var re RenderableError
if errors.As(err, &r) { if errors.As(err, &re) {
r.Render(w) re.Render(rw, r)
return return
} }
JSONStatus(w, err, statusCodeFromError(err)) JSONStatus(rw, r, err, statusCodeFromError(err))
} }
// StatusCodedError is the set of errors that implement the basic StatusCode // StatusCodedError is the set of errors that implement the basic StatusCode

@ -18,8 +18,8 @@ import (
func TestJSON(t *testing.T) { func TestJSON(t *testing.T) {
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
rw := logging.NewResponseLogger(rec) rw := logging.NewResponseLogger(rec)
r := httptest.NewRequest("POST", "/test", http.NoBody)
JSON(rw, map[string]interface{}{"foo": "bar"}) JSON(rw, r, map[string]interface{}{"foo": "bar"})
assert.Equal(t, http.StatusOK, rec.Result().StatusCode) assert.Equal(t, http.StatusOK, rec.Result().StatusCode)
assert.Equal(t, "application/json", rec.Header().Get("Content-Type")) assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
@ -64,7 +64,8 @@ func jsonPanicTest[T json.UnsupportedTypeError | json.UnsupportedValueError | js
assert.ErrorAs(t, err, &e) assert.ErrorAs(t, err, &e)
}() }()
JSON(httptest.NewRecorder(), v) r := httptest.NewRequest("POST", "/test", http.NoBody)
JSON(httptest.NewRecorder(), r, v)
} }
type renderableError struct { type renderableError struct {
@ -76,10 +77,9 @@ func (err renderableError) Error() string {
return err.Message return err.Message
} }
func (err renderableError) Render(w http.ResponseWriter) { func (err renderableError) Render(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "something/custom") w.Header().Set("Content-Type", "something/custom")
JSONStatus(w, r, err, err.Code)
JSONStatus(w, err, err.Code)
} }
type statusedError struct { type statusedError struct {
@ -116,8 +116,8 @@ func TestError(t *testing.T) {
t.Run(strconv.Itoa(caseIndex), func(t *testing.T) { t.Run(strconv.Itoa(caseIndex), func(t *testing.T) {
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/test", http.NoBody)
Error(rec, kase.err) Error(rec, r, kase.err)
assert.Equal(t, kase.code, rec.Result().StatusCode) assert.Equal(t, kase.code, rec.Result().StatusCode)
assert.Equal(t, kase.body, rec.Body.String()) assert.Equal(t, kase.body, rec.Body.String())

@ -23,19 +23,20 @@ func Renew(w http.ResponseWriter, r *http.Request) {
// Get the leaf certificate from the peer or the token. // Get the leaf certificate from the peer or the token.
cert, token, err := getPeerCertificate(r) cert, token, err := getPeerCertificate(r)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
// The token can be used by RAs to renew a certificate. // The token can be used by RAs to renew a certificate.
if token != "" { if token != "" {
ctx = authority.NewTokenContext(ctx, token) ctx = authority.NewTokenContext(ctx, token)
logOtt(w, token)
} }
a := mustAuthority(ctx) a := mustAuthority(ctx)
certChain, err := a.RenewContext(ctx, cert, nil) certChain, err := a.RenewContext(ctx, cert, nil)
if err != nil { if err != nil {
render.Error(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Renew")) render.Error(w, r, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Renew"))
return return
} }
certChainPEM := certChainToPEM(certChain) certChainPEM := certChainToPEM(certChain)
@ -45,7 +46,7 @@ func Renew(w http.ResponseWriter, r *http.Request) {
} }
LogCertificate(w, certChain[0]) LogCertificate(w, certChain[0])
render.JSONStatus(w, &SignResponse{ render.JSONStatus(w, r, &SignResponse{
ServerPEM: certChainPEM[0], ServerPEM: certChainPEM[0],
CaPEM: caPEM, CaPEM: caPEM,
CertChainPEM: certChainPEM, CertChainPEM: certChainPEM,

@ -57,12 +57,12 @@ func (r *RevokeRequest) Validate() (err error) {
func Revoke(w http.ResponseWriter, r *http.Request) { func Revoke(w http.ResponseWriter, r *http.Request) {
var body RevokeRequest var body RevokeRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body")) render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return return
} }
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -78,10 +78,10 @@ func Revoke(w http.ResponseWriter, r *http.Request) {
// A token indicates that we are using the api via a provisioner token, // A token indicates that we are using the api via a provisioner token,
// otherwise it is assumed that the certificate is revoking itself over mTLS. // otherwise it is assumed that the certificate is revoking itself over mTLS.
if len(body.OTT) > 0 { if body.OTT != "" {
logOtt(w, body.OTT) logOtt(w, body.OTT)
if _, err := a.Authorize(ctx, body.OTT); err != nil { if _, err := a.Authorize(ctx, body.OTT); err != nil {
render.Error(w, errs.UnauthorizedErr(err)) render.Error(w, r, errs.UnauthorizedErr(err))
return return
} }
opts.OTT = body.OTT opts.OTT = body.OTT
@ -90,12 +90,12 @@ func Revoke(w http.ResponseWriter, r *http.Request) {
// the client certificate Serial Number must match the serial number // the client certificate Serial Number must match the serial number
// being revoked. // being revoked.
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 { if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
render.Error(w, errs.BadRequest("missing ott or client certificate")) render.Error(w, r, errs.BadRequest("missing ott or client certificate"))
return return
} }
opts.Crt = r.TLS.PeerCertificates[0] opts.Crt = r.TLS.PeerCertificates[0]
if opts.Crt.SerialNumber.String() != opts.Serial { if opts.Crt.SerialNumber.String() != opts.Serial {
render.Error(w, errs.BadRequest("serial number in client certificate different than body")) render.Error(w, r, errs.BadRequest("serial number in client certificate different than body"))
return return
} }
// TODO: should probably be checking if the certificate was revoked here. // TODO: should probably be checking if the certificate was revoked here.
@ -106,12 +106,12 @@ func Revoke(w http.ResponseWriter, r *http.Request) {
} }
if err := a.Revoke(ctx, opts); err != nil { if err := a.Revoke(ctx, opts); err != nil {
render.Error(w, errs.ForbiddenErr(err, "error revoking certificate")) render.Error(w, r, errs.ForbiddenErr(err, "error revoking certificate"))
return return
} }
logRevoke(w, opts) logRevoke(w, opts)
render.JSON(w, &RevokeResponse{Status: "ok"}) render.JSON(w, r, &RevokeResponse{Status: "ok"})
} }
func logRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) { func logRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) {

@ -52,13 +52,13 @@ type SignResponse struct {
func Sign(w http.ResponseWriter, r *http.Request) { func Sign(w http.ResponseWriter, r *http.Request) {
var body SignRequest var body SignRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body")) render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return return
} }
logOtt(w, body.OTT) logOtt(w, body.OTT)
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -74,13 +74,13 @@ func Sign(w http.ResponseWriter, r *http.Request) {
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod)
signOpts, err := a.Authorize(ctx, body.OTT) signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil { if err != nil {
render.Error(w, errs.UnauthorizedErr(err)) render.Error(w, r, errs.UnauthorizedErr(err))
return return
} }
certChain, err := a.Sign(body.CsrPEM.CertificateRequest, opts, signOpts...) certChain, err := a.SignWithContext(ctx, body.CsrPEM.CertificateRequest, opts, signOpts...)
if err != nil { if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error signing certificate")) render.Error(w, r, errs.ForbiddenErr(err, "error signing certificate"))
return return
} }
certChainPEM := certChainToPEM(certChain) certChainPEM := certChainToPEM(certChain)
@ -90,7 +90,7 @@ func Sign(w http.ResponseWriter, r *http.Request) {
} }
LogCertificate(w, certChain[0]) LogCertificate(w, certChain[0])
render.JSONStatus(w, &SignResponse{ render.JSONStatus(w, r, &SignResponse{
ServerPEM: certChainPEM[0], ServerPEM: certChainPEM[0],
CaPEM: caPEM, CaPEM: caPEM,
CertChainPEM: certChainPEM, CertChainPEM: certChainPEM,

@ -253,19 +253,19 @@ type SSHBastionResponse struct {
func SSHSign(w http.ResponseWriter, r *http.Request) { func SSHSign(w http.ResponseWriter, r *http.Request) {
var body SSHSignRequest var body SSHSignRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body")) render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return return
} }
logOtt(w, body.OTT) logOtt(w, body.OTT)
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
publicKey, err := ssh.ParsePublicKey(body.PublicKey) publicKey, err := ssh.ParsePublicKey(body.PublicKey)
if err != nil { if err != nil {
render.Error(w, errs.BadRequestErr(err, "error parsing publicKey")) render.Error(w, r, errs.BadRequestErr(err, "error parsing publicKey"))
return return
} }
@ -273,7 +273,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
if body.AddUserPublicKey != nil { if body.AddUserPublicKey != nil {
addUserPublicKey, err = ssh.ParsePublicKey(body.AddUserPublicKey) addUserPublicKey, err = ssh.ParsePublicKey(body.AddUserPublicKey)
if err != nil { if err != nil {
render.Error(w, errs.BadRequestErr(err, "error parsing addUserPublicKey")) render.Error(w, r, errs.BadRequestErr(err, "error parsing addUserPublicKey"))
return return
} }
} }
@ -293,13 +293,13 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
a := mustAuthority(ctx) a := mustAuthority(ctx)
signOpts, err := a.Authorize(ctx, body.OTT) signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil { if err != nil {
render.Error(w, errs.UnauthorizedErr(err)) render.Error(w, r, errs.UnauthorizedErr(err))
return return
} }
cert, err := a.SignSSH(ctx, publicKey, opts, signOpts...) cert, err := a.SignSSH(ctx, publicKey, opts, signOpts...)
if err != nil { if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error signing ssh certificate")) render.Error(w, r, errs.ForbiddenErr(err, "error signing ssh certificate"))
return return
} }
@ -307,7 +307,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
if addUserPublicKey != nil && authority.IsValidForAddUser(cert) == nil { if addUserPublicKey != nil && authority.IsValidForAddUser(cert) == nil {
addUserCert, err := a.SignSSHAddUser(ctx, addUserPublicKey, cert) addUserCert, err := a.SignSSHAddUser(ctx, addUserPublicKey, cert)
if err != nil { if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error signing ssh certificate")) render.Error(w, r, errs.ForbiddenErr(err, "error signing ssh certificate"))
return return
} }
addUserCertificate = &SSHCertificate{addUserCert} addUserCertificate = &SSHCertificate{addUserCert}
@ -320,7 +320,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignIdentityMethod) ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignIdentityMethod)
signOpts, err := a.Authorize(ctx, body.OTT) signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil { if err != nil {
render.Error(w, errs.UnauthorizedErr(err)) render.Error(w, r, errs.UnauthorizedErr(err))
return return
} }
@ -330,16 +330,16 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
NotAfter: time.Unix(int64(cert.ValidBefore), 0), NotAfter: time.Unix(int64(cert.ValidBefore), 0),
}) })
certChain, err := a.Sign(cr, provisioner.SignOptions{}, signOpts...) certChain, err := a.SignWithContext(ctx, cr, provisioner.SignOptions{}, signOpts...)
if err != nil { if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error signing identity certificate")) render.Error(w, r, errs.ForbiddenErr(err, "error signing identity certificate"))
return return
} }
identityCertificate = certChainToPEM(certChain) identityCertificate = certChainToPEM(certChain)
} }
LogSSHCertificate(w, cert) LogSSHCertificate(w, cert)
render.JSONStatus(w, &SSHSignResponse{ render.JSONStatus(w, r, &SSHSignResponse{
Certificate: SSHCertificate{cert}, Certificate: SSHCertificate{cert},
AddUserCertificate: addUserCertificate, AddUserCertificate: addUserCertificate,
IdentityCertificate: identityCertificate, IdentityCertificate: identityCertificate,
@ -352,12 +352,12 @@ func SSHRoots(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
keys, err := mustAuthority(ctx).GetSSHRoots(ctx) keys, err := mustAuthority(ctx).GetSSHRoots(ctx)
if err != nil { if err != nil {
render.Error(w, errs.InternalServerErr(err)) render.Error(w, r, errs.InternalServerErr(err))
return return
} }
if len(keys.HostKeys) == 0 && len(keys.UserKeys) == 0 { if len(keys.HostKeys) == 0 && len(keys.UserKeys) == 0 {
render.Error(w, errs.NotFound("no keys found")) render.Error(w, r, errs.NotFound("no keys found"))
return return
} }
@ -369,7 +369,7 @@ func SSHRoots(w http.ResponseWriter, r *http.Request) {
resp.UserKeys = append(resp.UserKeys, SSHPublicKey{PublicKey: k}) resp.UserKeys = append(resp.UserKeys, SSHPublicKey{PublicKey: k})
} }
render.JSON(w, resp) render.JSON(w, r, resp)
} }
// SSHFederation is an HTTP handler that returns the federated SSH public keys // SSHFederation is an HTTP handler that returns the federated SSH public keys
@ -378,12 +378,12 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
keys, err := mustAuthority(ctx).GetSSHFederation(ctx) keys, err := mustAuthority(ctx).GetSSHFederation(ctx)
if err != nil { if err != nil {
render.Error(w, errs.InternalServerErr(err)) render.Error(w, r, errs.InternalServerErr(err))
return return
} }
if len(keys.HostKeys) == 0 && len(keys.UserKeys) == 0 { if len(keys.HostKeys) == 0 && len(keys.UserKeys) == 0 {
render.Error(w, errs.NotFound("no keys found")) render.Error(w, r, errs.NotFound("no keys found"))
return return
} }
@ -395,7 +395,7 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) {
resp.UserKeys = append(resp.UserKeys, SSHPublicKey{PublicKey: k}) resp.UserKeys = append(resp.UserKeys, SSHPublicKey{PublicKey: k})
} }
render.JSON(w, resp) render.JSON(w, r, resp)
} }
// SSHConfig is an HTTP handler that returns rendered templates for ssh clients // SSHConfig is an HTTP handler that returns rendered templates for ssh clients
@ -403,18 +403,18 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) {
func SSHConfig(w http.ResponseWriter, r *http.Request) { func SSHConfig(w http.ResponseWriter, r *http.Request) {
var body SSHConfigRequest var body SSHConfigRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body")) render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return return
} }
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
ctx := r.Context() ctx := r.Context()
ts, err := mustAuthority(ctx).GetSSHConfig(ctx, body.Type, body.Data) ts, err := mustAuthority(ctx).GetSSHConfig(ctx, body.Type, body.Data)
if err != nil { if err != nil {
render.Error(w, errs.InternalServerErr(err)) render.Error(w, r, errs.InternalServerErr(err))
return return
} }
@ -425,32 +425,32 @@ func SSHConfig(w http.ResponseWriter, r *http.Request) {
case provisioner.SSHHostCert: case provisioner.SSHHostCert:
cfg.HostTemplates = ts cfg.HostTemplates = ts
default: default:
render.Error(w, errs.InternalServer("it should hot get here")) render.Error(w, r, errs.InternalServer("it should hot get here"))
return return
} }
render.JSON(w, cfg) render.JSON(w, r, cfg)
} }
// SSHCheckHost is the HTTP handler that returns if a hosts certificate exists or not. // SSHCheckHost is the HTTP handler that returns if a hosts certificate exists or not.
func SSHCheckHost(w http.ResponseWriter, r *http.Request) { func SSHCheckHost(w http.ResponseWriter, r *http.Request) {
var body SSHCheckPrincipalRequest var body SSHCheckPrincipalRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body")) render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return return
} }
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
ctx := r.Context() ctx := r.Context()
exists, err := mustAuthority(ctx).CheckSSHHost(ctx, body.Principal, body.Token) exists, err := mustAuthority(ctx).CheckSSHHost(ctx, body.Principal, body.Token)
if err != nil { if err != nil {
render.Error(w, errs.InternalServerErr(err)) render.Error(w, r, errs.InternalServerErr(err))
return return
} }
render.JSON(w, &SSHCheckPrincipalResponse{ render.JSON(w, r, &SSHCheckPrincipalResponse{
Exists: exists, Exists: exists,
}) })
} }
@ -465,10 +465,10 @@ func SSHGetHosts(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
hosts, err := mustAuthority(ctx).GetSSHHosts(ctx, cert) hosts, err := mustAuthority(ctx).GetSSHHosts(ctx, cert)
if err != nil { if err != nil {
render.Error(w, errs.InternalServerErr(err)) render.Error(w, r, errs.InternalServerErr(err))
return return
} }
render.JSON(w, &SSHGetHostsResponse{ render.JSON(w, r, &SSHGetHostsResponse{
Hosts: hosts, Hosts: hosts,
}) })
} }
@ -477,22 +477,22 @@ func SSHGetHosts(w http.ResponseWriter, r *http.Request) {
func SSHBastion(w http.ResponseWriter, r *http.Request) { func SSHBastion(w http.ResponseWriter, r *http.Request) {
var body SSHBastionRequest var body SSHBastionRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body")) render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return return
} }
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
ctx := r.Context() ctx := r.Context()
bastion, err := mustAuthority(ctx).GetSSHBastion(ctx, body.User, body.Hostname) bastion, err := mustAuthority(ctx).GetSSHBastion(ctx, body.User, body.Hostname)
if err != nil { if err != nil {
render.Error(w, errs.InternalServerErr(err)) render.Error(w, r, errs.InternalServerErr(err))
return return
} }
render.JSON(w, &SSHBastionResponse{ render.JSON(w, r, &SSHBastionResponse{
Hostname: body.Hostname, Hostname: body.Hostname,
Bastion: bastion, Bastion: bastion,
}) })

@ -42,19 +42,19 @@ type SSHRekeyResponse struct {
func SSHRekey(w http.ResponseWriter, r *http.Request) { func SSHRekey(w http.ResponseWriter, r *http.Request) {
var body SSHRekeyRequest var body SSHRekeyRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body")) render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return return
} }
logOtt(w, body.OTT) logOtt(w, body.OTT)
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
publicKey, err := ssh.ParsePublicKey(body.PublicKey) publicKey, err := ssh.ParsePublicKey(body.PublicKey)
if err != nil { if err != nil {
render.Error(w, errs.BadRequestErr(err, "error parsing publicKey")) render.Error(w, r, errs.BadRequestErr(err, "error parsing publicKey"))
return return
} }
@ -64,18 +64,18 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) {
a := mustAuthority(ctx) a := mustAuthority(ctx)
signOpts, err := a.Authorize(ctx, body.OTT) signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil { if err != nil {
render.Error(w, errs.UnauthorizedErr(err)) render.Error(w, r, errs.UnauthorizedErr(err))
return return
} }
oldCert, _, err := provisioner.ExtractSSHPOPCert(body.OTT) oldCert, _, err := provisioner.ExtractSSHPOPCert(body.OTT)
if err != nil { if err != nil {
render.Error(w, errs.InternalServerErr(err)) render.Error(w, r, errs.InternalServerErr(err))
return return
} }
newCert, err := a.RekeySSH(ctx, oldCert, publicKey, signOpts...) newCert, err := a.RekeySSH(ctx, oldCert, publicKey, signOpts...)
if err != nil { if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error rekeying ssh certificate")) render.Error(w, r, errs.ForbiddenErr(err, "error rekeying ssh certificate"))
return return
} }
@ -85,12 +85,12 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) {
identity, err := renewIdentityCertificate(r, notBefore, notAfter) identity, err := renewIdentityCertificate(r, notBefore, notAfter)
if err != nil { if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error renewing identity certificate")) render.Error(w, r, errs.ForbiddenErr(err, "error renewing identity certificate"))
return return
} }
LogSSHCertificate(w, newCert) LogSSHCertificate(w, newCert)
render.JSONStatus(w, &SSHRekeyResponse{ render.JSONStatus(w, r, &SSHRekeyResponse{
Certificate: SSHCertificate{newCert}, Certificate: SSHCertificate{newCert},
IdentityCertificate: identity, IdentityCertificate: identity,
}, http.StatusCreated) }, http.StatusCreated)

@ -40,13 +40,13 @@ type SSHRenewResponse struct {
func SSHRenew(w http.ResponseWriter, r *http.Request) { func SSHRenew(w http.ResponseWriter, r *http.Request) {
var body SSHRenewRequest var body SSHRenewRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body")) render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return return
} }
logOtt(w, body.OTT) logOtt(w, body.OTT)
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -56,18 +56,18 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) {
a := mustAuthority(ctx) a := mustAuthority(ctx)
_, err := a.Authorize(ctx, body.OTT) _, err := a.Authorize(ctx, body.OTT)
if err != nil { if err != nil {
render.Error(w, errs.UnauthorizedErr(err)) render.Error(w, r, errs.UnauthorizedErr(err))
return return
} }
oldCert, _, err := provisioner.ExtractSSHPOPCert(body.OTT) oldCert, _, err := provisioner.ExtractSSHPOPCert(body.OTT)
if err != nil { if err != nil {
render.Error(w, errs.InternalServerErr(err)) render.Error(w, r, errs.InternalServerErr(err))
return return
} }
newCert, err := a.RenewSSH(ctx, oldCert) newCert, err := a.RenewSSH(ctx, oldCert)
if err != nil { if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error renewing ssh certificate")) render.Error(w, r, errs.ForbiddenErr(err, "error renewing ssh certificate"))
return return
} }
@ -77,12 +77,12 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) {
identity, err := renewIdentityCertificate(r, notBefore, notAfter) identity, err := renewIdentityCertificate(r, notBefore, notAfter)
if err != nil { if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error renewing identity certificate")) render.Error(w, r, errs.ForbiddenErr(err, "error renewing identity certificate"))
return return
} }
LogSSHCertificate(w, newCert) LogSSHCertificate(w, newCert)
render.JSONStatus(w, &SSHSignResponse{ render.JSONStatus(w, r, &SSHSignResponse{
Certificate: SSHCertificate{newCert}, Certificate: SSHCertificate{newCert},
IdentityCertificate: identity, IdentityCertificate: identity,
}, http.StatusCreated) }, http.StatusCreated)

@ -51,12 +51,12 @@ func (r *SSHRevokeRequest) Validate() (err error) {
func SSHRevoke(w http.ResponseWriter, r *http.Request) { func SSHRevoke(w http.ResponseWriter, r *http.Request) {
var body SSHRevokeRequest var body SSHRevokeRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body")) render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return return
} }
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -75,18 +75,18 @@ func SSHRevoke(w http.ResponseWriter, r *http.Request) {
logOtt(w, body.OTT) logOtt(w, body.OTT)
if _, err := a.Authorize(ctx, body.OTT); err != nil { if _, err := a.Authorize(ctx, body.OTT); err != nil {
render.Error(w, errs.UnauthorizedErr(err)) render.Error(w, r, errs.UnauthorizedErr(err))
return return
} }
opts.OTT = body.OTT opts.OTT = body.OTT
if err := a.Revoke(ctx, opts); err != nil { if err := a.Revoke(ctx, opts); err != nil {
render.Error(w, errs.ForbiddenErr(err, "error revoking ssh certificate")) render.Error(w, r, errs.ForbiddenErr(err, "error revoking ssh certificate"))
return return
} }
logSSHRevoke(w, opts) logSSHRevoke(w, opts)
render.JSON(w, &SSHRevokeResponse{Status: "ok"}) render.JSON(w, r, &SSHRevokeResponse{Status: "ok"})
} }
func logSSHRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) { func logSSHRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) {

@ -325,7 +325,7 @@ func Test_SSHSign(t *testing.T) {
signSSHAddUser: func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) { signSSHAddUser: func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) {
return tt.addUserCert, tt.addUserErr return tt.addUserCert, tt.addUserErr
}, },
sign: func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { signWithContext: func(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
return tt.tlsSignCerts, tt.tlsSignErr return tt.tlsSignCerts, tt.tlsSignErr
}, },
}) })

@ -40,12 +40,12 @@ func requireEABEnabled(next http.HandlerFunc) http.HandlerFunc {
acmeProvisioner := prov.GetDetails().GetACME() acmeProvisioner := prov.GetDetails().GetACME()
if acmeProvisioner == nil { if acmeProvisioner == nil {
render.Error(w, admin.NewErrorISE("error getting ACME details for provisioner '%s'", prov.GetName())) render.Error(w, r, admin.NewErrorISE("error getting ACME details for provisioner '%s'", prov.GetName()))
return return
} }
if !acmeProvisioner.RequireEab { if !acmeProvisioner.RequireEab {
render.Error(w, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner '%s'", prov.GetName())) render.Error(w, r, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner '%s'", prov.GetName()))
return return
} }
@ -69,18 +69,18 @@ func NewACMEAdminResponder() ACMEAdminResponder {
} }
// GetExternalAccountKeys writes the response for the EAB keys GET endpoint // GetExternalAccountKeys writes the response for the EAB keys GET endpoint
func (h *acmeAdminResponder) GetExternalAccountKeys(w http.ResponseWriter, _ *http.Request) { func (h *acmeAdminResponder) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm")) render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
} }
// CreateExternalAccountKey writes the response for the EAB key POST endpoint // CreateExternalAccountKey writes the response for the EAB key POST endpoint
func (h *acmeAdminResponder) CreateExternalAccountKey(w http.ResponseWriter, _ *http.Request) { func (h *acmeAdminResponder) CreateExternalAccountKey(w http.ResponseWriter, r *http.Request) {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm")) render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
} }
// DeleteExternalAccountKey writes the response for the EAB key DELETE endpoint // DeleteExternalAccountKey writes the response for the EAB key DELETE endpoint
func (h *acmeAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, _ *http.Request) { func (h *acmeAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Request) {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm")) render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
} }
func eakToLinked(k *acme.ExternalAccountKey) *linkedca.EABKey { func eakToLinked(k *acme.ExternalAccountKey) *linkedca.EABKey {

@ -90,7 +90,7 @@ func GetAdmin(w http.ResponseWriter, r *http.Request) {
adm, ok := mustAuthority(r.Context()).LoadAdminByID(id) adm, ok := mustAuthority(r.Context()).LoadAdminByID(id)
if !ok { if !ok {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, render.Error(w, r, admin.NewError(admin.ErrorNotFoundType,
"admin %s not found", id)) "admin %s not found", id))
return return
} }
@ -101,17 +101,17 @@ func GetAdmin(w http.ResponseWriter, r *http.Request) {
func GetAdmins(w http.ResponseWriter, r *http.Request) { func GetAdmins(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := api.ParseCursor(r) cursor, limit, err := api.ParseCursor(r)
if err != nil { if err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err,
"error parsing cursor and limit from query params")) "error parsing cursor and limit from query params"))
return return
} }
admins, nextCursor, err := mustAuthority(r.Context()).GetAdmins(cursor, limit) admins, nextCursor, err := mustAuthority(r.Context()).GetAdmins(cursor, limit)
if err != nil { if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error retrieving paginated admins")) render.Error(w, r, admin.WrapErrorISE(err, "error retrieving paginated admins"))
return return
} }
render.JSON(w, &GetAdminsResponse{ render.JSON(w, r, &GetAdminsResponse{
Admins: admins, Admins: admins,
NextCursor: nextCursor, NextCursor: nextCursor,
}) })
@ -121,19 +121,19 @@ func GetAdmins(w http.ResponseWriter, r *http.Request) {
func CreateAdmin(w http.ResponseWriter, r *http.Request) { func CreateAdmin(w http.ResponseWriter, r *http.Request) {
var body CreateAdminRequest var body CreateAdminRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
return return
} }
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
auth := mustAuthority(r.Context()) auth := mustAuthority(r.Context())
p, err := auth.LoadProvisionerByName(body.Provisioner) p, err := auth.LoadProvisionerByName(body.Provisioner)
if err != nil { if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner)) render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner))
return return
} }
adm := &linkedca.Admin{ adm := &linkedca.Admin{
@ -143,7 +143,7 @@ func CreateAdmin(w http.ResponseWriter, r *http.Request) {
} }
// Store to authority collection. // Store to authority collection.
if err := auth.StoreAdmin(r.Context(), adm, p); err != nil { if err := auth.StoreAdmin(r.Context(), adm, p); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error storing admin")) render.Error(w, r, admin.WrapErrorISE(err, "error storing admin"))
return return
} }
@ -155,23 +155,23 @@ func DeleteAdmin(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id") id := chi.URLParam(r, "id")
if err := mustAuthority(r.Context()).RemoveAdmin(r.Context(), id); err != nil { if err := mustAuthority(r.Context()).RemoveAdmin(r.Context(), id); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error deleting admin %s", id)) render.Error(w, r, admin.WrapErrorISE(err, "error deleting admin %s", id))
return return
} }
render.JSON(w, &DeleteResponse{Status: "ok"}) render.JSON(w, r, &DeleteResponse{Status: "ok"})
} }
// UpdateAdmin updates an existing admin. // UpdateAdmin updates an existing admin.
func UpdateAdmin(w http.ResponseWriter, r *http.Request) { func UpdateAdmin(w http.ResponseWriter, r *http.Request) {
var body UpdateAdminRequest var body UpdateAdminRequest
if err := read.JSON(r.Body, &body); err != nil { if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
return return
} }
if err := body.Validate(); err != nil { if err := body.Validate(); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -179,7 +179,7 @@ func UpdateAdmin(w http.ResponseWriter, r *http.Request) {
auth := mustAuthority(r.Context()) auth := mustAuthority(r.Context())
adm, err := auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type}) adm, err := auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type})
if err != nil { if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error updating admin %s", id)) render.Error(w, r, admin.WrapErrorISE(err, "error updating admin %s", id))
return return
} }

@ -1,7 +1,6 @@
package api package api
import ( import (
"errors"
"net/http" "net/http"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@ -20,7 +19,7 @@ import (
func requireAPIEnabled(next http.HandlerFunc) http.HandlerFunc { func requireAPIEnabled(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if !mustAuthority(r.Context()).IsAdminAPIEnabled() { if !mustAuthority(r.Context()).IsAdminAPIEnabled() {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "administration API not enabled")) render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "administration API not enabled"))
return return
} }
next(w, r) next(w, r)
@ -32,7 +31,7 @@ func extractAuthorizeTokenAdmin(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
tok := r.Header.Get("Authorization") tok := r.Header.Get("Authorization")
if tok == "" { if tok == "" {
render.Error(w, admin.NewError(admin.ErrorUnauthorizedType, render.Error(w, r, admin.NewError(admin.ErrorUnauthorizedType,
"missing authorization header token")) "missing authorization header token"))
return return
} }
@ -40,7 +39,7 @@ func extractAuthorizeTokenAdmin(next http.HandlerFunc) http.HandlerFunc {
ctx := r.Context() ctx := r.Context()
adm, err := mustAuthority(ctx).AuthorizeAdminToken(r, tok) adm, err := mustAuthority(ctx).AuthorizeAdminToken(r, tok)
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -65,13 +64,13 @@ func loadProvisionerByName(next http.HandlerFunc) http.HandlerFunc {
// TODO(hs): distinguish 404 vs. 500 // TODO(hs): distinguish 404 vs. 500
if p, err = auth.LoadProvisionerByName(name); err != nil { if p, err = auth.LoadProvisionerByName(name); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name))
return return
} }
prov, err := adminDB.GetProvisioner(ctx, p.GetID()) prov, err := adminDB.GetProvisioner(ctx, p.GetID())
if err != nil { if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error retrieving provisioner %s", name)) render.Error(w, r, admin.WrapErrorISE(err, "error retrieving provisioner %s", name))
return return
} }
@ -92,7 +91,7 @@ func checkAction(next http.HandlerFunc, supportedInStandalone bool) http.Handler
// when an action is not supported in standalone mode and when // when an action is not supported in standalone mode and when
// using a nosql.DB backend, actions are not supported // using a nosql.DB backend, actions are not supported
if _, ok := admin.MustFromContext(r.Context()).(*nosql.DB); ok { if _, ok := admin.MustFromContext(r.Context()).(*nosql.DB); ok {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType,
"operation not supported in standalone mode")) "operation not supported in standalone mode"))
return return
} }
@ -125,16 +124,16 @@ func loadExternalAccountKey(next http.HandlerFunc) http.HandlerFunc {
} }
if err != nil { if err != nil {
if errors.Is(err, acme.ErrNotFound) { if acme.IsErrNotFound(err) {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found")) render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found"))
return return
} }
render.Error(w, admin.WrapErrorISE(err, "error retrieving ACME External Account Key")) render.Error(w, r, admin.WrapErrorISE(err, "error retrieving ACME External Account Key"))
return return
} }
if eak == nil { if eak == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found")) render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found"))
return return
} }

@ -35,7 +35,7 @@ type PolicyAdminResponder interface {
// policyAdminResponder implements PolicyAdminResponder. // policyAdminResponder implements PolicyAdminResponder.
type policyAdminResponder struct{} type policyAdminResponder struct{}
// NewACMEAdminResponder returns a new PolicyAdminResponder. // NewPolicyAdminResponder returns a new PolicyAdminResponder.
func NewPolicyAdminResponder() PolicyAdminResponder { func NewPolicyAdminResponder() PolicyAdminResponder {
return &policyAdminResponder{} return &policyAdminResponder{}
} }
@ -44,7 +44,7 @@ func NewPolicyAdminResponder() PolicyAdminResponder {
func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -52,12 +52,12 @@ func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *ht
authorityPolicy, err := auth.GetAuthorityPolicy(r.Context()) authorityPolicy, err := auth.GetAuthorityPolicy(r.Context())
var ae *admin.Error var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, admin.WrapErrorISE(ae, "error retrieving authority policy")) render.Error(w, r, admin.WrapErrorISE(ae, "error retrieving authority policy"))
return return
} }
if authorityPolicy == nil { if authorityPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist")) render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
return return
} }
@ -68,7 +68,7 @@ func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *ht
func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -77,26 +77,26 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r
var ae *admin.Error var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, admin.WrapErrorISE(err, "error retrieving authority policy")) render.Error(w, r, admin.WrapErrorISE(err, "error retrieving authority policy"))
return return
} }
if authorityPolicy != nil { if authorityPolicy != nil {
adminErr := admin.NewError(admin.ErrorConflictType, "authority already has a policy") adminErr := admin.NewError(admin.ErrorConflictType, "authority already has a policy")
render.Error(w, adminErr) render.Error(w, r, adminErr)
return return
} }
var newPolicy = new(linkedca.Policy) var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil { if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil { if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
return return
} }
@ -105,11 +105,11 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r
var createdPolicy *linkedca.Policy var createdPolicy *linkedca.Policy
if createdPolicy, err = auth.CreateAuthorityPolicy(ctx, adm, newPolicy); err != nil { if createdPolicy, err = auth.CreateAuthorityPolicy(ctx, adm, newPolicy); err != nil {
if isBadRequest(err) { if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error storing authority policy")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error storing authority policy"))
return return
} }
render.Error(w, admin.WrapErrorISE(err, "error storing authority policy")) render.Error(w, r, admin.WrapErrorISE(err, "error storing authority policy"))
return return
} }
@ -120,7 +120,7 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r
func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -129,25 +129,25 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r
var ae *admin.Error var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, admin.WrapErrorISE(err, "error retrieving authority policy")) render.Error(w, r, admin.WrapErrorISE(err, "error retrieving authority policy"))
return return
} }
if authorityPolicy == nil { if authorityPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist")) render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
return return
} }
var newPolicy = new(linkedca.Policy) var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil { if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil { if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
return return
} }
@ -156,11 +156,11 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r
var updatedPolicy *linkedca.Policy var updatedPolicy *linkedca.Policy
if updatedPolicy, err = auth.UpdateAuthorityPolicy(ctx, adm, newPolicy); err != nil { if updatedPolicy, err = auth.UpdateAuthorityPolicy(ctx, adm, newPolicy); err != nil {
if isBadRequest(err) { if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating authority policy")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating authority policy"))
return return
} }
render.Error(w, admin.WrapErrorISE(err, "error updating authority policy")) render.Error(w, r, admin.WrapErrorISE(err, "error updating authority policy"))
return return
} }
@ -171,7 +171,7 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r
func (par *policyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -180,35 +180,35 @@ func (par *policyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r
var ae *admin.Error var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, admin.WrapErrorISE(ae, "error retrieving authority policy")) render.Error(w, r, admin.WrapErrorISE(ae, "error retrieving authority policy"))
return return
} }
if authorityPolicy == nil { if authorityPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist")) render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
return return
} }
if err := auth.RemoveAuthorityPolicy(ctx); err != nil { if err := auth.RemoveAuthorityPolicy(ctx); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error deleting authority policy")) render.Error(w, r, admin.WrapErrorISE(err, "error deleting authority policy"))
return return
} }
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
} }
// GetProvisionerPolicy handles the GET /admin/provisioners/{name}/policy request // GetProvisionerPolicy handles the GET /admin/provisioners/{name}/policy request
func (par *policyAdminResponder) GetProvisionerPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) GetProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
prov := linkedca.MustProvisionerFromContext(ctx) prov := linkedca.MustProvisionerFromContext(ctx)
provisionerPolicy := prov.GetPolicy() provisionerPolicy := prov.GetPolicy()
if provisionerPolicy == nil { if provisionerPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist")) render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
return return
} }
@ -219,7 +219,7 @@ func (par *policyAdminResponder) GetProvisionerPolicy(w http.ResponseWriter, r *
func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -227,20 +227,20 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter,
provisionerPolicy := prov.GetPolicy() provisionerPolicy := prov.GetPolicy()
if provisionerPolicy != nil { if provisionerPolicy != nil {
adminErr := admin.NewError(admin.ErrorConflictType, "provisioner %s already has a policy", prov.Name) adminErr := admin.NewError(admin.ErrorConflictType, "provisioner %s already has a policy", prov.Name)
render.Error(w, adminErr) render.Error(w, r, adminErr)
return return
} }
var newPolicy = new(linkedca.Policy) var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil { if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil { if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
return return
} }
@ -248,11 +248,11 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter,
auth := mustAuthority(ctx) auth := mustAuthority(ctx)
if err := auth.UpdateProvisioner(ctx, prov); err != nil { if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) { if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner policy")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner policy"))
return return
} }
render.Error(w, admin.WrapErrorISE(err, "error creating provisioner policy")) render.Error(w, r, admin.WrapErrorISE(err, "error creating provisioner policy"))
return return
} }
@ -263,27 +263,27 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
prov := linkedca.MustProvisionerFromContext(ctx) prov := linkedca.MustProvisionerFromContext(ctx)
provisionerPolicy := prov.GetPolicy() provisionerPolicy := prov.GetPolicy()
if provisionerPolicy == nil { if provisionerPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist")) render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
return return
} }
var newPolicy = new(linkedca.Policy) var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil { if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil { if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
return return
} }
@ -291,11 +291,11 @@ func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter,
auth := mustAuthority(ctx) auth := mustAuthority(ctx)
if err := auth.UpdateProvisioner(ctx, prov); err != nil { if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) { if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner policy")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner policy"))
return return
} }
render.Error(w, admin.WrapErrorISE(err, "error updating provisioner policy")) render.Error(w, r, admin.WrapErrorISE(err, "error updating provisioner policy"))
return return
} }
@ -306,13 +306,13 @@ func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
prov := linkedca.MustProvisionerFromContext(ctx) prov := linkedca.MustProvisionerFromContext(ctx)
if prov.Policy == nil { if prov.Policy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist")) render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
return return
} }
@ -321,24 +321,24 @@ func (par *policyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter,
auth := mustAuthority(ctx) auth := mustAuthority(ctx)
if err := auth.UpdateProvisioner(ctx, prov); err != nil { if err := auth.UpdateProvisioner(ctx, prov); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error deleting provisioner policy")) render.Error(w, r, admin.WrapErrorISE(err, "error deleting provisioner policy"))
return return
} }
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
} }
func (par *policyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
eak := linkedca.MustExternalAccountKeyFromContext(ctx) eak := linkedca.MustExternalAccountKeyFromContext(ctx)
eakPolicy := eak.GetPolicy() eakPolicy := eak.GetPolicy()
if eakPolicy == nil { if eakPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist")) render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
return return
} }
@ -348,7 +348,7 @@ func (par *policyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r *
func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -357,20 +357,20 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter,
eakPolicy := eak.GetPolicy() eakPolicy := eak.GetPolicy()
if eakPolicy != nil { if eakPolicy != nil {
adminErr := admin.NewError(admin.ErrorConflictType, "ACME EAK %s already has a policy", eak.Id) adminErr := admin.NewError(admin.ErrorConflictType, "ACME EAK %s already has a policy", eak.Id)
render.Error(w, adminErr) render.Error(w, r, adminErr)
return return
} }
var newPolicy = new(linkedca.Policy) var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil { if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil { if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
return return
} }
@ -379,7 +379,7 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter,
acmeEAK := linkedEAKToCertificates(eak) acmeEAK := linkedEAKToCertificates(eak)
acmeDB := acme.MustDatabaseFromContext(ctx) acmeDB := acme.MustDatabaseFromContext(ctx)
if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil { if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error creating ACME EAK policy")) render.Error(w, r, admin.WrapErrorISE(err, "error creating ACME EAK policy"))
return return
} }
@ -389,7 +389,7 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -397,20 +397,20 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter,
eak := linkedca.MustExternalAccountKeyFromContext(ctx) eak := linkedca.MustExternalAccountKeyFromContext(ctx)
eakPolicy := eak.GetPolicy() eakPolicy := eak.GetPolicy()
if eakPolicy == nil { if eakPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist")) render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
return return
} }
var newPolicy = new(linkedca.Policy) var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil { if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil { if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
return return
} }
@ -418,7 +418,7 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter,
acmeEAK := linkedEAKToCertificates(eak) acmeEAK := linkedEAKToCertificates(eak)
acmeDB := acme.MustDatabaseFromContext(ctx) acmeDB := acme.MustDatabaseFromContext(ctx)
if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil { if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error updating ACME EAK policy")) render.Error(w, r, admin.WrapErrorISE(err, "error updating ACME EAK policy"))
return return
} }
@ -428,7 +428,7 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil { if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -436,7 +436,7 @@ func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter,
eak := linkedca.MustExternalAccountKeyFromContext(ctx) eak := linkedca.MustExternalAccountKeyFromContext(ctx)
eakPolicy := eak.GetPolicy() eakPolicy := eak.GetPolicy()
if eakPolicy == nil { if eakPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist")) render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
return return
} }
@ -446,11 +446,11 @@ func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter,
acmeEAK := linkedEAKToCertificates(eak) acmeEAK := linkedEAKToCertificates(eak)
acmeDB := acme.MustDatabaseFromContext(ctx) acmeDB := acme.MustDatabaseFromContext(ctx)
if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil { if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error deleting ACME EAK policy")) render.Error(w, r, admin.WrapErrorISE(err, "error deleting ACME EAK policy"))
return return
} }
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
} }
// blockLinkedCA blocks all API operations on linked deployments // blockLinkedCA blocks all API operations on linked deployments

@ -38,21 +38,21 @@ func GetProvisioner(w http.ResponseWriter, r *http.Request) {
auth := mustAuthority(ctx) auth := mustAuthority(ctx)
db := admin.MustFromContext(ctx) db := admin.MustFromContext(ctx)
if len(id) > 0 { if id != "" {
if p, err = auth.LoadProvisionerByID(id); err != nil { if p, err = auth.LoadProvisionerByID(id); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id)) render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", id))
return return
} }
} else { } else {
if p, err = auth.LoadProvisionerByName(name); err != nil { if p, err = auth.LoadProvisionerByName(name); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name))
return return
} }
} }
prov, err := db.GetProvisioner(ctx, p.GetID()) prov, err := db.GetProvisioner(ctx, p.GetID())
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
render.ProtoJSON(w, prov) render.ProtoJSON(w, prov)
@ -62,17 +62,17 @@ func GetProvisioner(w http.ResponseWriter, r *http.Request) {
func GetProvisioners(w http.ResponseWriter, r *http.Request) { func GetProvisioners(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := api.ParseCursor(r) cursor, limit, err := api.ParseCursor(r)
if err != nil { if err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err,
"error parsing cursor and limit from query params")) "error parsing cursor and limit from query params"))
return return
} }
p, next, err := mustAuthority(r.Context()).GetProvisioners(cursor, limit) p, next, err := mustAuthority(r.Context()).GetProvisioners(cursor, limit)
if err != nil { if err != nil {
render.Error(w, errs.InternalServerErr(err)) render.Error(w, r, errs.InternalServerErr(err))
return return
} }
render.JSON(w, &GetProvisionersResponse{ render.JSON(w, r, &GetProvisionersResponse{
Provisioners: p, Provisioners: p,
NextCursor: next, NextCursor: next,
}) })
@ -82,24 +82,24 @@ func GetProvisioners(w http.ResponseWriter, r *http.Request) {
func CreateProvisioner(w http.ResponseWriter, r *http.Request) { func CreateProvisioner(w http.ResponseWriter, r *http.Request) {
var prov = new(linkedca.Provisioner) var prov = new(linkedca.Provisioner)
if err := read.ProtoJSON(r.Body, prov); err != nil { if err := read.ProtoJSON(r.Body, prov); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
// TODO: Validate inputs // TODO: Validate inputs
if err := authority.ValidateClaims(prov.Claims); err != nil { if err := authority.ValidateClaims(prov.Claims); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
// validate the templates and template data // validate the templates and template data
if err := validateTemplates(prov.X509Template, prov.SshTemplate); err != nil { if err := validateTemplates(prov.X509Template, prov.SshTemplate); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template"))
return return
} }
if err := mustAuthority(r.Context()).StoreProvisioner(r.Context(), prov); err != nil { if err := mustAuthority(r.Context()).StoreProvisioner(r.Context(), prov); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name)) render.Error(w, r, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name))
return return
} }
render.ProtoJSONStatus(w, prov, http.StatusCreated) render.ProtoJSONStatus(w, prov, http.StatusCreated)
@ -116,31 +116,31 @@ func DeleteProvisioner(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name") name := chi.URLParam(r, "name")
auth := mustAuthority(r.Context()) auth := mustAuthority(r.Context())
if len(id) > 0 { if id != "" {
if p, err = auth.LoadProvisionerByID(id); err != nil { if p, err = auth.LoadProvisionerByID(id); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id)) render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", id))
return return
} }
} else { } else {
if p, err = auth.LoadProvisionerByName(name); err != nil { if p, err = auth.LoadProvisionerByName(name); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name))
return return
} }
} }
if err := auth.RemoveProvisioner(r.Context(), p.GetID()); err != nil { if err := auth.RemoveProvisioner(r.Context(), p.GetID()); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName())) render.Error(w, r, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName()))
return return
} }
render.JSON(w, &DeleteResponse{Status: "ok"}) render.JSON(w, r, &DeleteResponse{Status: "ok"})
} }
// UpdateProvisioner updates an existing prov. // UpdateProvisioner updates an existing prov.
func UpdateProvisioner(w http.ResponseWriter, r *http.Request) { func UpdateProvisioner(w http.ResponseWriter, r *http.Request) {
var nu = new(linkedca.Provisioner) var nu = new(linkedca.Provisioner)
if err := read.ProtoJSON(r.Body, nu); err != nil { if err := read.ProtoJSON(r.Body, nu); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -151,51 +151,51 @@ func UpdateProvisioner(w http.ResponseWriter, r *http.Request) {
p, err := auth.LoadProvisionerByName(name) p, err := auth.LoadProvisionerByName(name)
if err != nil { if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name)) render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name))
return return
} }
old, err := db.GetProvisioner(r.Context(), p.GetID()) old, err := db.GetProvisioner(r.Context(), p.GetID())
if err != nil { if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", p.GetID())) render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", p.GetID()))
return return
} }
if nu.Id != old.Id { if nu.Id != old.Id {
render.Error(w, admin.NewErrorISE("cannot change provisioner ID")) render.Error(w, r, admin.NewErrorISE("cannot change provisioner ID"))
return return
} }
if nu.Type != old.Type { if nu.Type != old.Type {
render.Error(w, admin.NewErrorISE("cannot change provisioner type")) render.Error(w, r, admin.NewErrorISE("cannot change provisioner type"))
return return
} }
if nu.AuthorityId != old.AuthorityId { if nu.AuthorityId != old.AuthorityId {
render.Error(w, admin.NewErrorISE("cannot change provisioner authorityID")) render.Error(w, r, admin.NewErrorISE("cannot change provisioner authorityID"))
return return
} }
if !nu.CreatedAt.AsTime().Equal(old.CreatedAt.AsTime()) { if !nu.CreatedAt.AsTime().Equal(old.CreatedAt.AsTime()) {
render.Error(w, admin.NewErrorISE("cannot change provisioner createdAt")) render.Error(w, r, admin.NewErrorISE("cannot change provisioner createdAt"))
return return
} }
if !nu.DeletedAt.AsTime().Equal(old.DeletedAt.AsTime()) { if !nu.DeletedAt.AsTime().Equal(old.DeletedAt.AsTime()) {
render.Error(w, admin.NewErrorISE("cannot change provisioner deletedAt")) render.Error(w, r, admin.NewErrorISE("cannot change provisioner deletedAt"))
return return
} }
// TODO: Validate inputs // TODO: Validate inputs
if err := authority.ValidateClaims(nu.Claims); err != nil { if err := authority.ValidateClaims(nu.Claims); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
// validate the templates and template data // validate the templates and template data
if err := validateTemplates(nu.X509Template, nu.SshTemplate); err != nil { if err := validateTemplates(nu.X509Template, nu.SshTemplate); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template"))
return return
} }
if err := auth.UpdateProvisioner(r.Context(), nu); err != nil { if err := auth.UpdateProvisioner(r.Context(), nu); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
render.ProtoJSON(w, nu) render.ProtoJSON(w, nu)

@ -71,28 +71,28 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter
var newWebhook = new(linkedca.Webhook) var newWebhook = new(linkedca.Webhook)
if err := read.ProtoJSON(r.Body, newWebhook); err != nil { if err := read.ProtoJSON(r.Body, newWebhook); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
if err := validateWebhook(newWebhook); err != nil { if err := validateWebhook(newWebhook); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
if newWebhook.Secret != "" { if newWebhook.Secret != "" {
err := admin.NewError(admin.ErrorBadRequestType, "webhook secret must not be set") err := admin.NewError(admin.ErrorBadRequestType, "webhook secret must not be set")
render.Error(w, err) render.Error(w, r, err)
return return
} }
if newWebhook.Id != "" { if newWebhook.Id != "" {
err := admin.NewError(admin.ErrorBadRequestType, "webhook ID must not be set") err := admin.NewError(admin.ErrorBadRequestType, "webhook ID must not be set")
render.Error(w, err) render.Error(w, r, err)
return return
} }
id, err := randutil.UUIDv4() id, err := randutil.UUIDv4()
if err != nil { if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error generating webhook id")) render.Error(w, r, admin.WrapErrorISE(err, "error generating webhook id"))
return return
} }
newWebhook.Id = id newWebhook.Id = id
@ -101,14 +101,14 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter
for _, wh := range prov.Webhooks { for _, wh := range prov.Webhooks {
if wh.Name == newWebhook.Name { if wh.Name == newWebhook.Name {
err := admin.NewError(admin.ErrorConflictType, "provisioner %q already has a webhook with the name %q", prov.Name, newWebhook.Name) err := admin.NewError(admin.ErrorConflictType, "provisioner %q already has a webhook with the name %q", prov.Name, newWebhook.Name)
render.Error(w, err) render.Error(w, r, err)
return return
} }
} }
secret, err := randutil.Bytes(64) secret, err := randutil.Bytes(64)
if err != nil { if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error generating webhook secret")) render.Error(w, r, admin.WrapErrorISE(err, "error generating webhook secret"))
return return
} }
newWebhook.Secret = base64.StdEncoding.EncodeToString(secret) newWebhook.Secret = base64.StdEncoding.EncodeToString(secret)
@ -117,11 +117,11 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter
if err := auth.UpdateProvisioner(ctx, prov); err != nil { if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) { if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner webhook")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner webhook"))
return return
} }
render.Error(w, admin.WrapErrorISE(err, "error creating provisioner webhook")) render.Error(w, r, admin.WrapErrorISE(err, "error creating provisioner webhook"))
return return
} }
@ -145,21 +145,21 @@ func (war *webhookAdminResponder) DeleteProvisionerWebhook(w http.ResponseWriter
} }
} }
if !found { if !found {
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
return return
} }
if err := auth.UpdateProvisioner(ctx, prov); err != nil { if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) { if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error deleting provisioner webhook")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error deleting provisioner webhook"))
return return
} }
render.Error(w, admin.WrapErrorISE(err, "error deleting provisioner webhook")) render.Error(w, r, admin.WrapErrorISE(err, "error deleting provisioner webhook"))
return return
} }
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
} }
func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter, r *http.Request) { func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter, r *http.Request) {
@ -170,12 +170,12 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter
var newWebhook = new(linkedca.Webhook) var newWebhook = new(linkedca.Webhook)
if err := read.ProtoJSON(r.Body, newWebhook); err != nil { if err := read.ProtoJSON(r.Body, newWebhook); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
if err := validateWebhook(newWebhook); err != nil { if err := validateWebhook(newWebhook); err != nil {
render.Error(w, err) render.Error(w, r, err)
return return
} }
@ -186,13 +186,13 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter
} }
if newWebhook.Secret != "" && newWebhook.Secret != wh.Secret { if newWebhook.Secret != "" && newWebhook.Secret != wh.Secret {
err := admin.NewError(admin.ErrorBadRequestType, "webhook secret cannot be updated") err := admin.NewError(admin.ErrorBadRequestType, "webhook secret cannot be updated")
render.Error(w, err) render.Error(w, r, err)
return return
} }
newWebhook.Secret = wh.Secret newWebhook.Secret = wh.Secret
if newWebhook.Id != "" && newWebhook.Id != wh.Id { if newWebhook.Id != "" && newWebhook.Id != wh.Id {
err := admin.NewError(admin.ErrorBadRequestType, "webhook ID cannot be updated") err := admin.NewError(admin.ErrorBadRequestType, "webhook ID cannot be updated")
render.Error(w, err) render.Error(w, r, err)
return return
} }
newWebhook.Id = wh.Id newWebhook.Id = wh.Id
@ -203,17 +203,17 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter
if !found { if !found {
msg := fmt.Sprintf("provisioner %q has no webhook with the name %q", prov.Name, newWebhook.Name) msg := fmt.Sprintf("provisioner %q has no webhook with the name %q", prov.Name, newWebhook.Name)
err := admin.NewError(admin.ErrorNotFoundType, msg) err := admin.NewError(admin.ErrorNotFoundType, msg)
render.Error(w, err) render.Error(w, r, err)
return return
} }
if err := auth.UpdateProvisioner(ctx, prov); err != nil { if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) { if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner webhook")) render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner webhook"))
return return
} }
render.Error(w, admin.WrapErrorISE(err, "error updating provisioner webhook")) render.Error(w, r, admin.WrapErrorISE(err, "error updating provisioner webhook"))
return return
} }

@ -857,7 +857,7 @@ func TestDB_CreateAdmin(t *testing.T) {
var _dba = new(dbAdmin) var _dba = new(dbAdmin)
assert.FatalError(t, json.Unmarshal(nu, _dba)) assert.FatalError(t, json.Unmarshal(nu, _dba))
assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key)) assert.True(t, _dba.ID != "" && _dba.ID == string(key))
assert.Equals(t, _dba.AuthorityID, adm.AuthorityId) assert.Equals(t, _dba.AuthorityID, adm.AuthorityId)
assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId) assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId)
assert.Equals(t, _dba.Subject, adm.Subject) assert.Equals(t, _dba.Subject, adm.Subject)
@ -890,7 +890,7 @@ func TestDB_CreateAdmin(t *testing.T) {
var _dba = new(dbAdmin) var _dba = new(dbAdmin)
assert.FatalError(t, json.Unmarshal(nu, _dba)) assert.FatalError(t, json.Unmarshal(nu, _dba))
assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key)) assert.True(t, _dba.ID != "" && _dba.ID == string(key))
assert.Equals(t, _dba.AuthorityID, adm.AuthorityId) assert.Equals(t, _dba.AuthorityID, adm.AuthorityId)
assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId) assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId)
assert.Equals(t, _dba.Subject, adm.Subject) assert.Equals(t, _dba.Subject, adm.Subject)

@ -906,7 +906,7 @@ func TestDB_CreateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner) var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp)) assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type) assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name) assert.Equals(t, _dbp.Name, prov.Name)
@ -944,7 +944,7 @@ func TestDB_CreateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner) var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp)) assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type) assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name) assert.Equals(t, _dbp.Name, prov.Name)
@ -1093,7 +1093,7 @@ func TestDB_UpdateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner) var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp)) assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type) assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name) assert.Equals(t, _dbp.Name, prov.Name)
@ -1188,7 +1188,7 @@ func TestDB_UpdateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner) var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp)) assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type) assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name) assert.Equals(t, _dbp.Name, prov.Name)

@ -205,8 +205,8 @@ func (e *Error) ToLog() (interface{}, error) {
} }
// Render implements render.RenderableError for Error. // Render implements render.RenderableError for Error.
func (e *Error) Render(w http.ResponseWriter) { func (e *Error) Render(w http.ResponseWriter, r *http.Request) {
e.Message = e.Err.Error() e.Message = e.Err.Error()
render.JSONStatus(w, e, e.StatusCode()) render.JSONStatus(w, r, e, e.StatusCode())
} }

@ -62,9 +62,10 @@ type Authority struct {
x509Enforcers []provisioner.CertificateEnforcer x509Enforcers []provisioner.CertificateEnforcer
// SCEP CA // SCEP CA
scepOptions *scep.Options scepOptions *scep.Options
validateSCEP bool validateSCEP bool
scepAuthority *scep.Authority scepAuthority *scep.Authority
scepKeyManager provisioner.SCEPKeyManager
// SSH CA // SSH CA
sshHostPassword []byte sshHostPassword []byte
@ -104,6 +105,9 @@ type Authority struct {
// If true, do not output initialization logs // If true, do not output initialization logs
quietInit bool quietInit bool
// Called whenever applicable, in order to instrument the authority.
meter Meter
} }
// Info contains information about the authority. // Info contains information about the authority.
@ -126,6 +130,7 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) {
config: cfg, config: cfg,
certificates: new(sync.Map), certificates: new(sync.Map),
validateSCEP: true, validateSCEP: true,
meter: noopMeter{},
} }
// Apply options. // Apply options.
@ -134,6 +139,9 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) {
return nil, err return nil, err
} }
} }
if a.keyManager != nil {
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
}
if !a.skipInit { if !a.skipInit {
// Initialize authority from options or configuration. // Initialize authority from options or configuration.
@ -151,6 +159,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
a := &Authority{ a := &Authority{
config: &config.Config{}, config: &config.Config{},
certificates: new(sync.Map), certificates: new(sync.Map),
meter: noopMeter{},
} }
// Apply options. // Apply options.
@ -159,6 +168,9 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
return nil, err return nil, err
} }
} }
if a.keyManager != nil {
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
}
// Validate required options // Validate required options
switch { switch {
@ -337,6 +349,8 @@ func (a *Authority) init() error {
if err != nil { if err != nil {
return err return err
} }
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
} }
// Initialize linkedca client if necessary. On a linked RA, the issuer // Initialize linkedca client if necessary. On a linked RA, the issuer
@ -433,6 +447,7 @@ func (a *Authority) init() error {
return err return err
} }
a.rootX509Certs = append(a.rootX509Certs, resp.RootCertificate) a.rootX509Certs = append(a.rootX509Certs, resp.RootCertificate)
a.intermediateX509Certs = append(a.intermediateX509Certs, resp.IntermediateCertificates...)
} }
} }
@ -681,32 +696,42 @@ func (a *Authority) init() error {
options := &scep.Options{ options := &scep.Options{
Roots: a.rootX509Certs, Roots: a.rootX509Certs,
Intermediates: a.intermediateX509Certs, Intermediates: a.intermediateX509Certs,
SignerCert: a.intermediateX509Certs[0],
} }
if options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey, // intermediate certificates can be empty in RA mode
Password: a.password, if len(a.intermediateX509Certs) > 0 {
}); err != nil { options.SignerCert = a.intermediateX509Certs[0]
return err
} }
// TODO(hs): instead of creating the decrypter here, pass the
// intermediate key + chain down to the SCEP authority, // attempt to create the (default) SCEP signer if the intermediate
// and only instantiate it when required there. Is that possible? // key is configured.
// Also with entering passwords? if a.config.IntermediateKey != "" {
// TODO(hs): if moving the logic, try improving the logic for the if options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
// decrypter password too? Right now it needs to be entered multiple SigningKey: a.config.IntermediateKey,
// times; I've observed it to be three times maximum, every time Password: a.password,
// the intermediate key is read. }); err != nil {
_, isRSA := options.Signer.Public().(*rsa.PublicKey) return err
if km, ok := a.keyManager.(kmsapi.Decrypter); ok && isRSA { }
if decrypter, err := km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey, // TODO(hs): instead of creating the decrypter here, pass the
Password: a.password, // intermediate key + chain down to the SCEP authority,
}); err == nil { // and only instantiate it when required there. Is that possible?
// only pass the decrypter down when it was successfully created, // Also with entering passwords?
// meaning it's an RSA key, and `CreateDecrypter` did not fail. // TODO(hs): if moving the logic, try improving the logic for the
options.Decrypter = decrypter // decrypter password too? Right now it needs to be entered multiple
options.DecrypterCert = options.Intermediates[0] // times; I've observed it to be three times maximum, every time
// the intermediate key is read.
_, isRSAKey := options.Signer.Public().(*rsa.PublicKey)
if km, ok := a.keyManager.(kmsapi.Decrypter); ok && isRSAKey {
if decrypter, err := km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey,
Password: a.password,
}); err == nil {
// only pass the decrypter down when it was successfully created,
// meaning it's an RSA key, and `CreateDecrypter` did not fail.
options.Decrypter = decrypter
options.DecrypterCert = options.Intermediates[0]
}
} }
} }

@ -1,6 +1,7 @@
package authority package authority
import ( import (
"context"
"crypto" "crypto"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
@ -112,7 +113,7 @@ func TestAuthorityNew(t *testing.T) {
c.Root = []string{"foo"} c.Root = []string{"foo"}
return &newTest{ return &newTest{
config: c, config: c,
err: errors.New("error reading foo: no such file or directory"), err: errors.New(`error reading "foo": no such file or directory`),
} }
}, },
"fail bad password": func(t *testing.T) *newTest { "fail bad password": func(t *testing.T) *newTest {
@ -130,7 +131,7 @@ func TestAuthorityNew(t *testing.T) {
c.IntermediateCert = "wrong" c.IntermediateCert = "wrong"
return &newTest{ return &newTest{
config: c, config: c,
err: errors.New("error reading wrong: no such file or directory"), err: errors.New(`error reading "wrong": no such file or directory`),
} }
}, },
} }
@ -414,7 +415,7 @@ func TestNewEmbedded_Sign(t *testing.T) {
csr, err := x509.ParseCertificateRequest(cr) csr, err := x509.ParseCertificateRequest(cr)
assert.FatalError(t, err) assert.FatalError(t, err)
cert, err := a.Sign(csr, provisioner.SignOptions{}) cert, err := a.SignWithContext(context.Background(), csr, provisioner.SignOptions{})
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, []string{"foo.bar.zar"}, cert[0].DNSNames) assert.Equals(t, []string{"foo.bar.zar"}, cert[0].DNSNames)
assert.Equals(t, crt, cert[1]) assert.Equals(t, crt, cert[1])

@ -286,16 +286,16 @@ func (a *Authority) authorizeRevoke(ctx context.Context, token string) error {
// extra extension cannot be found, authorize the renewal by default. // extra extension cannot be found, authorize the renewal by default.
// //
// TODO(mariano): should we authorize by default? // TODO(mariano): should we authorize by default?
func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) error { func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) (provisioner.Interface, error) {
serial := cert.SerialNumber.String() serial := cert.SerialNumber.String()
var opts = []interface{}{errs.WithKeyVal("serialNumber", serial)} var opts = []interface{}{errs.WithKeyVal("serialNumber", serial)}
isRevoked, err := a.IsRevoked(serial) isRevoked, err := a.IsRevoked(serial)
if err != nil { if err != nil {
return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
} }
if isRevoked { if isRevoked {
return errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...) return nil, errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...)
} }
p, err := a.LoadProvisionerByCertificate(cert) p, err := a.LoadProvisionerByCertificate(cert)
if err != nil { if err != nil {
@ -305,13 +305,13 @@ func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate)
// returns the noop provisioner if this happens, and it allows // returns the noop provisioner if this happens, and it allows
// certificate renewals. // certificate renewals.
if p, ok = a.provisioners.LoadByCertificate(cert); !ok { if p, ok = a.provisioners.LoadByCertificate(cert); !ok {
return errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...) return nil, errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...)
} }
} }
if err := p.AuthorizeRenew(ctx, cert); err != nil { if err := p.AuthorizeRenew(ctx, cert); err != nil {
return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
} }
return nil return p, nil
} }
// authorizeSSHCertificate returns an error if the given certificate is revoked. // authorizeSSHCertificate returns an error if the given certificate is revoked.

@ -876,7 +876,7 @@ func TestAuthority_authorizeRenew(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := genTestCase(t) tc := genTestCase(t)
err := tc.auth.authorizeRenew(context.Background(), tc.cert) _, err := tc.auth.authorizeRenew(context.Background(), tc.cert)
if err != nil { if err != nil {
if assert.NotNil(t, tc.err) { if assert.NotNil(t, tc.err) {
var sc render.StatusCodedError var sc render.StatusCodedError
@ -1375,7 +1375,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
} }
generateX5cToken := func(a *Authority, key crypto.Signer, claims jose.Claims, opts ...provisioner.SignOption) (string, *x509.Certificate) { generateX5cToken := func(a *Authority, key crypto.Signer, claims jose.Claims, opts ...provisioner.SignOption) (string, *x509.Certificate) {
chain, err := a.Sign(csr, provisioner.SignOptions{}, opts...) chain, err := a.SignWithContext(ctx, csr, provisioner.SignOptions{}, opts...)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -83,6 +83,7 @@ type Config struct {
Templates *templates.Templates `json:"templates,omitempty"` Templates *templates.Templates `json:"templates,omitempty"`
CommonName string `json:"commonName,omitempty"` CommonName string `json:"commonName,omitempty"`
CRL *CRLConfig `json:"crl,omitempty"` CRL *CRLConfig `json:"crl,omitempty"`
MetricsAddress string `json:"metricsAddress,omitempty"`
SkipValidation bool `json:"-"` SkipValidation bool `json:"-"`
// Keeps record of the filename the Config is read from // Keeps record of the filename the Config is read from
@ -327,6 +328,12 @@ func (c *Config) Validate() error {
return errors.Errorf("invalid address %s", c.Address) return errors.Errorf("invalid address %s", c.Address)
} }
if addr := c.MetricsAddress; addr != "" {
if _, _, err := net.SplitHostPort(addr); err != nil {
return errors.Errorf("invalid metrics address %q", c.Address)
}
}
if c.TLS == nil { if c.TLS == nil {
c.TLS = &DefaultTLSOptions c.TLS = &DefaultTLSOptions
} else { } else {

@ -203,7 +203,7 @@ func matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
// domainToReverseLabels converts a textual domain name like foo.example.com to // domainToReverseLabels converts a textual domain name like foo.example.com to
// the list of labels in reverse order, e.g. ["com", "example", "foo"]. // the list of labels in reverse order, e.g. ["com", "example", "foo"].
func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) {
for len(domain) > 0 { for domain != "" {
if i := strings.LastIndexByte(domain, '.'); i == -1 { if i := strings.LastIndexByte(domain, '.'); i == -1 {
reverseLabels = append(reverseLabels, domain) reverseLabels = append(reverseLabels, domain)
domain = "" domain = ""
@ -316,7 +316,7 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
} else { } else {
// Atom ("." Atom)* // Atom ("." Atom)*
NextChar: NextChar:
for len(in) > 0 { for in != "" {
// atext from RFC 2822, Section 3.2.4 // atext from RFC 2822, Section 3.2.4
c := in[0] c := in[0]

@ -110,7 +110,7 @@ func newLinkedCAClient(token string) (*linkedCaClient, error) {
tlsConfig.GetClientCertificate = renewer.GetClientCertificate tlsConfig.GetClientCertificate = renewer.GetClientCertificate
// Start mTLS client // Start mTLS client
conn, err := grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) conn, err := grpc.NewClient(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "error connecting %s", u.Host) return nil, errors.Wrapf(err, "error connecting %s", u.Host)
} }
@ -478,10 +478,7 @@ func getAuthority(sans []string) (string, error) {
// getRootCertificate creates an insecure majordomo client and returns the // getRootCertificate creates an insecure majordomo client and returns the
// verified root certificate. // verified root certificate.
func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error) { func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error) {
ctx, cancel := context.WithCancel(context.Background()) conn, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
defer cancel()
conn, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
//nolint:gosec // used in bootstrap protocol //nolint:gosec // used in bootstrap protocol
InsecureSkipVerify: true, // lgtm[go/disabled-certificate-check] InsecureSkipVerify: true, // lgtm[go/disabled-certificate-check]
}))) })))
@ -489,7 +486,7 @@ func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error)
return nil, errors.Wrapf(err, "error connecting %s", endpoint) return nil, errors.Wrapf(err, "error connecting %s", endpoint)
} }
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel() defer cancel()
client := linkedca.NewMajordomoClient(conn) client := linkedca.NewMajordomoClient(conn)
@ -531,11 +528,7 @@ func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error)
// login creates a new majordomo client with just the root ca pool and returns // login creates a new majordomo client with just the root ca pool and returns
// the signed certificate and tls configuration. // the signed certificate and tls configuration.
func login(authority, token string, csr *x509.CertificateRequest, signer crypto.PrivateKey, endpoint string, rootCAs *x509.CertPool) (*tls.Certificate, *tls.Config, error) { func login(authority, token string, csr *x509.CertificateRequest, signer crypto.PrivateKey, endpoint string, rootCAs *x509.CertPool) (*tls.Certificate, *tls.Config, error) {
// Connect to majordomo conn, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conn, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
RootCAs: rootCAs, RootCAs: rootCAs,
}))) })))
@ -544,7 +537,7 @@ func login(authority, token string, csr *x509.CertificateRequest, signer crypto.
} }
// Login to get the signed certificate // Login to get the signed certificate
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel() defer cancel()
client := linkedca.NewMajordomoClient(conn) client := linkedca.NewMajordomoClient(conn)

@ -0,0 +1,111 @@
package authority
import (
"crypto"
"io"
"go.step.sm/crypto/kms"
kmsapi "go.step.sm/crypto/kms/apiv1"
"github.com/smallstep/certificates/authority/provisioner"
)
// Meter wraps the set of defined callbacks for metrics gatherers.
type Meter interface {
// X509Signed is called whenever an X509 certificate is signed.
X509Signed(provisioner.Interface, error)
// X509Renewed is called whenever an X509 certificate is renewed.
X509Renewed(provisioner.Interface, error)
// X509Rekeyed is called whenever an X509 certificate is rekeyed.
X509Rekeyed(provisioner.Interface, error)
// X509WebhookAuthorized is called whenever an X509 authoring webhook is called.
X509WebhookAuthorized(provisioner.Interface, error)
// X509WebhookEnriched is called whenever an X509 enriching webhook is called.
X509WebhookEnriched(provisioner.Interface, error)
// SSHSigned is called whenever an SSH certificate is signed.
SSHSigned(provisioner.Interface, error)
// SSHRenewed is called whenever an SSH certificate is renewed.
SSHRenewed(provisioner.Interface, error)
// SSHRekeyed is called whenever an SSH certificate is rekeyed.
SSHRekeyed(provisioner.Interface, error)
// SSHWebhookAuthorized is called whenever an SSH authoring webhook is called.
SSHWebhookAuthorized(provisioner.Interface, error)
// SSHWebhookEnriched is called whenever an SSH enriching webhook is called.
SSHWebhookEnriched(provisioner.Interface, error)
// KMSSigned is called per KMS signer signature.
KMSSigned(error)
}
// noopMeter implements a noop [Meter].
type noopMeter struct{}
func (noopMeter) SSHRekeyed(provisioner.Interface, error) {}
func (noopMeter) SSHRenewed(provisioner.Interface, error) {}
func (noopMeter) SSHSigned(provisioner.Interface, error) {}
func (noopMeter) SSHWebhookAuthorized(provisioner.Interface, error) {}
func (noopMeter) SSHWebhookEnriched(provisioner.Interface, error) {}
func (noopMeter) X509Rekeyed(provisioner.Interface, error) {}
func (noopMeter) X509Renewed(provisioner.Interface, error) {}
func (noopMeter) X509Signed(provisioner.Interface, error) {}
func (noopMeter) X509WebhookAuthorized(provisioner.Interface, error) {}
func (noopMeter) X509WebhookEnriched(provisioner.Interface, error) {}
func (noopMeter) KMSSigned(error) {}
type instrumentedKeyManager struct {
kms.KeyManager
meter Meter
}
type instrumentedKeyAndDecrypterManager struct {
kms.KeyManager
decrypter kmsapi.Decrypter
meter Meter
}
func newInstrumentedKeyManager(k kms.KeyManager, m Meter) kms.KeyManager {
decrypter, isDecrypter := k.(kmsapi.Decrypter)
switch {
case isDecrypter:
return &instrumentedKeyAndDecrypterManager{&instrumentedKeyManager{k, m}, decrypter, m}
default:
return &instrumentedKeyManager{k, m}
}
}
func (i *instrumentedKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (s crypto.Signer, err error) {
if s, err = i.KeyManager.CreateSigner(req); err == nil {
s = &instrumentedKMSSigner{s, i.meter}
}
return
}
func (i *instrumentedKeyAndDecrypterManager) CreateDecrypter(req *kmsapi.CreateDecrypterRequest) (s crypto.Decrypter, err error) {
return i.decrypter.CreateDecrypter(req)
}
type instrumentedKMSSigner struct {
crypto.Signer
meter Meter
}
func (i *instrumentedKMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
signature, err = i.Signer.Sign(rand, digest, opts)
i.meter.KMSSigned(err)
return
}
var _ kms.KeyManager = (*instrumentedKeyManager)(nil)
var _ kms.KeyManager = (*instrumentedKeyAndDecrypterManager)(nil)
var _ kmsapi.Decrypter = (*instrumentedKeyAndDecrypterManager)(nil)

@ -167,6 +167,15 @@ func WithKeyManager(k kms.KeyManager) Option {
} }
} }
// WithX509CAService allows the consumer to provide an externally implemented
// API implementation of apiv1.CertificateAuthorityService
func WithX509CAService(svc casapi.CertificateAuthorityService) Option {
return func(a *Authority) error {
a.x509CAService = svc
return nil
}
}
// WithX509Signer defines the signer used to sign X509 certificates. // WithX509Signer defines the signer used to sign X509 certificates.
func WithX509Signer(crt *x509.Certificate, s crypto.Signer) Option { func WithX509Signer(crt *x509.Certificate, s crypto.Signer) Option {
return WithX509SignerChain([]*x509.Certificate{crt}, s) return WithX509SignerChain([]*x509.Certificate{crt}, s)
@ -217,6 +226,16 @@ func WithFullSCEPOptions(options *scep.Options) Option {
} }
} }
// WithSCEPKeyManager defines the key manager used on SCEP provisioners.
//
// This feature is EXPERIMENTAL and might change at any time.
func WithSCEPKeyManager(skm provisioner.SCEPKeyManager) Option {
return func(a *Authority) error {
a.scepKeyManager = skm
return nil
}
}
// WithSSHUserSigner defines the signer used to sign SSH user certificates. // WithSSHUserSigner defines the signer used to sign SSH user certificates.
func WithSSHUserSigner(s crypto.Signer) Option { func WithSSHUserSigner(s crypto.Signer) Option {
return func(a *Authority) error { return func(a *Authority) error {
@ -381,3 +400,16 @@ func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) {
} }
return certs, nil return certs, nil
} }
// WithMeter is an option that sets the authority's [Meter] to the provided one.
func WithMeter(m Meter) Option {
if m == nil {
m = noopMeter{}
}
return func(a *Authority) (_ error) {
a.meter = m
return
}
}

@ -21,6 +21,7 @@ type HostPolicy policy.SSHNamePolicyEngine
func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy, error) { func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy, error) {
// return early if no policy engine options to configure // return early if no policy engine options to configure
if policyOptions == nil { if policyOptions == nil {
//nolint:nilnil,nolintlint // expected values
return nil, nil return nil, nil
} }
@ -50,6 +51,7 @@ func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy,
// ensure no policy engine is returned when no name options were provided // ensure no policy engine is returned when no name options were provided
if len(options) == 0 { if len(options) == 0 {
//nolint:nilnil,nolintlint // expected values
return nil, nil return nil, nil
} }
@ -93,6 +95,7 @@ func NewSSHHostPolicyEngine(policyOptions SSHPolicyOptionsInterface) (HostPolicy
func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEngineType) (policy.SSHNamePolicyEngine, error) { func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEngineType) (policy.SSHNamePolicyEngine, error) {
// return early if no policy engine options to configure // return early if no policy engine options to configure
if policyOptions == nil { if policyOptions == nil {
//nolint:nilnil,nolintlint // expected values
return nil, nil return nil, nil
} }
@ -134,6 +137,7 @@ func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEn
// ensure no policy engine is returned when no name options were provided // ensure no policy engine is returned when no name options were provided
if len(options) == 0 { if len(options) == 0 {
//nolint:nilnil,nolintlint // expected values
return nil, nil return nil, nil
} }

@ -1,24 +1,89 @@
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-signature.html # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-signature.html
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/regions-certs.html use RSA format
# default certificate for "other regions"
-----BEGIN CERTIFICATE----- # certificate for us-east-2
MIIDIjCCAougAwIBAgIJAKnL4UEDMN/FMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV -----BEGIN CERTIFICATE-----
BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRgw MIIDITCCAoqgAwIBAgIUVJTc+hOU+8Gk3JlqsX438Dk5c58wDQYJKoZIhvcNAQEL
FgYDVQQKEw9BbWF6b24uY29tIEluYy4xGjAYBgNVBAMTEWVjMi5hbWF6b25hd3Mu BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
Y29tMB4XDTE0MDYwNTE0MjgwMloXDTI0MDYwNTE0MjgwMlowajELMAkGA1UEBhMC BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxGDAWBgNV MB4XDTI0MDQyOTE3MTE0OVoXDTI5MDQyODE3MTE0OVowXDELMAkGA1UEBhMCVVMx
BAoTD0FtYXpvbi5jb20gSW5jLjEaMBgGA1UEAxMRZWMyLmFtYXpvbmF3cy5jb20w GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIe9GN//SRK2knbjySG0ho3yqQM3 BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
e2TDhWO8D2e8+XZqck754gFSo99AbT2RmXClambI7xsYHZFapbELC4H91ycihvrD A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
jbST1ZjkLQgga0NE1q43eS68ZeTDccScXQSNivSlzJZS8HJZjgqzBlXjZftjtdJL UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
XeE4hwvo0sD4f3j9AgMBAAGjgc8wgcwwHQYDVR0OBBYEFCXWzAgVyrbwnFncFFIs vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
77VBdlE4MIGcBgNVHSMEgZQwgZGAFCXWzAgVyrbwnFncFFIs77VBdlE4oW6kbDBq o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2Vh UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
dHRsZTEYMBYGA1UEChMPQW1hem9uLmNvbSBJbmMuMRowGAYDVQQDExFlYzIuYW1h BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
em9uYXdzLmNvbYIJAKnL4UEDMN/FMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUVJTc+hOU
BQADgYEAFYcz1OgEhQBXIwIdsgCOS8vEtiJYF+j9uO6jz7VOmJqO+pRlAbRlvY8T +8Gk3JlqsX438Dk5c58wEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
C1haGgSI/A1uZUKs/Zfnph0oEI0/hu1IIJ/SKBDtN5lvmZ/IzbOPIJWirlsllQIQ AAOBgQAywJQaVNWJqW0R0T0xVOSoN1GLk9x9kKEuN67RN9CLin4dA97qa7Mr5W4P
7zvWbGd9c9+Rm3p04oTvhup99la7kZqevJK0QRdD/6NpCKsqP/0= FZ6vnh5CjOhQBRXV9xJUeYSdqVItNAUFK/fEzDdjf1nUfPlQ3OJ49u6CV01NoJ9m
usvY9kWcV46dqn2bk2MyfTTgvmeqP8fiMRPxxnVRkSzlldP5Fg==
-----END CERTIFICATE-----
# certificate for us-east-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUE1y2NIKCU+Rg4uu4u32koG9QEYIwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE3MzQwMVoXDTI5MDQyODE3MzQwMVowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUE1y2NIKC
U+Rg4uu4u32koG9QEYIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQAlxSmwcWnhT4uAeSinJuz+1BTcKhVSWb5jT8pYjQb8ZoZkXXRGb09mvYeU
NeqOBr27rvRAnaQ/9LUQf72+SahDFuS4CMI8nwowytqbmwquqFr4dxA/SDADyRiF
ea1UoMuNHTY49J/1vPomqsVn7mugTp+TbjqCfOJTpu0temHcFA==
-----END CERTIFICATE-----
# certificate for us-west-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUK2zmY9PUSTR7rc1k2OwPYu4+g7wwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE3MDI0M1oXDTI5MDQyODE3MDI0M1owXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUK2zmY9PU
STR7rc1k2OwPYu4+g7wwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQA1Ng4QmN4n7iPh5CnadSOc0ZfM7by0dBePwZJyGvOHdaw6P6E/vEk76KsC
Q8p+akuzVzVPkU4kBK/TRqLp19wEWoVwhhTaxHjQ1tTRHqXIVlrkw4JrtFbeNM21
GlkSLonuzmNZdivn9WuQYeGe7nUD4w3q9GgiF3CPorJe+UxtbA==
-----END CERTIFICATE-----
# certificate for us-west-2
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUFx8PxCkbHwpD31bOyCtyz3GclbgwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE3MjM1OVoXDTI5MDQyODE3MjM1OVowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUFx8PxCkb
HwpD31bOyCtyz3GclbgwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBzOl+9Xy1+UsbUBI95HO9mbbdnuX+aMJXgG9uFZNjgNEbMcvx+h8P9IMko
z7PzFdheQQ1NLjsHH9mSR1SyC4m9ja6BsejH5nLBWyCdjfdP3muZM4O5+r7vUa1O
dWU+hP/T7DUrPAIVMOE7mpYa+WPWJrN6BlRwQkKQ7twm9kDalA==
-----END CERTIFICATE----- -----END CERTIFICATE-----
# certificate for eu-south-1 # certificate for eu-south-1
@ -92,7 +157,7 @@ NTpxxcXmUKquX+pHmIkK1LKDO8rNE84jqxrxRsfDi6by82fjVYf2pgjJW8R1FAw+
mL5WQRFexbfB5aXhcMo0AA== mL5WQRFexbfB5aXhcMo0AA==
-----END CERTIFICATE----- -----END CERTIFICATE-----
# certificate for cn-north-1, cn-northwest-1 # certificate for cn-north-1
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDCzCCAnSgAwIBAgIJALSOMbOoU2svMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV MIIDCzCCAnSgAwIBAgIJALSOMbOoU2svMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0 BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0
@ -113,6 +178,48 @@ oADS0ph+YUz5P/bUCm61wFjlxaTfwKcuTR3ytj7bFLoW5Bm7Sa+TCl3lOGb2taon
SUDlRyNy1jJFstEZjOhs SUDlRyNy1jJFstEZjOhs
-----END CERTIFICATE----- -----END CERTIFICATE-----
# certificate for cn-northwest-1
-----BEGIN CERTIFICATE-----
MIIDCzCCAnSgAwIBAgIJALSOMbOoU2svMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0
dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0yMzA3MDQw
ODM1MzlaFw0yODA3MDIwODM1MzlaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBX
YXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6
b24gV2ViIFNlcnZpY2VzIExMQzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
uhhUNlqAZdcWWB/OSDVDGk3OA99EFzOn/mJlmciQ/Xwu2dFJWmSCqEAE6gjufCjQ
q3voxAhC2CF+elKtJW/C0Sz/LYo60PUqd6iXF4h+upB9HkOOGuWHXsHBTsvgkgGA
1CGgel4U0Cdq+23eANr8N8m28UzljjSnTlrYCHtzN4sCAwEAAaOB1DCB0TALBgNV
HQ8EBAMCB4AwHQYDVR0OBBYEFBkZu3wT27NnYgrfH+xJz4HJaNJoMIGOBgNVHSME
gYYwgYOAFBkZu3wT27NnYgrfH+xJz4HJaNJooWCkXjBcMQswCQYDVQQGEwJVUzEZ
MBcGA1UECBMQV2FzaGluZ3RvbiBTdGF0ZTEQMA4GA1UEBxMHU2VhdHRsZTEgMB4G
A1UEChMXQW1hem9uIFdlYiBTZXJ2aWNlcyBMTEOCCQC0jjGzqFNrLzASBgNVHRMB
Af8ECDAGAQH/AgEAMA0GCSqGSIb3DQEBCwUAA4GBAECji43p+oPkYqmzll7e8Hgb
oADS0ph+YUz5P/bUCm61wFjlxaTfwKcuTR3ytj7bFLoW5Bm7Sa+TCl3lOGb2taon
2h+9NirRK6JYk87LMNvbS40HGPFumJL2NzEsGUeK+MRiWu+Oh5/lJGii3qw4YByx
SUDlRyNy1jJFstEZjOhs
-----END CERTIFICATE-----
# certificate for eu-central-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUFD5GsmkxRuecttwsCG763m3u63UwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE1NTUyOVoXDTI5MDQyODE1NTUyOVowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUFD5Gsmkx
RuecttwsCG763m3u63UwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBBh0WaXlBsW56Hqk588MmJxsOrvcKfDjF57RgEDgnGnQaJcStCVWDO9UYO
JX2tdsPw+E7AjDqjsuxYaotLn3Mr3mK0sNOXq9BljBnWD4pARg89KZnZI8FN35HQ
O/LYOVHCknuPL123VmVRNs51qQA9hkPjvw21UzpDLxaUxt9Z/w==
-----END CERTIFICATE-----
# certificate for eu-central-2 # certificate for eu-central-2
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAXjSGFGiMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT MIICMzCCAZygAwIBAgIGAXjSGFGiMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
@ -129,6 +236,27 @@ NBElvPCDKFvTJl4QQhToy056llO5GvdS9RK+H8xrP2mrqngApoKTApv93vHBixgF
Sn5KrczRO0YSm3OjkqbydU7DFlmkXXR7GYE+5jbHvQHYiT1J5sMu Sn5KrczRO0YSm3OjkqbydU7DFlmkXXR7GYE+5jbHvQHYiT1J5sMu
-----END CERTIFICATE----- -----END CERTIFICATE-----
# certificate for ap-south-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUDLA+x6tTAP3LRTr0z6nOxfsozdMwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE0MTMwMVoXDTI5MDQyODE0MTMwMVowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUDLA+x6tT
AP3LRTr0z6nOxfsozdMwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQAZ7rYKoAwwiiH1M5GJbrT/BEk3OO2VrEPw8ZxgpqQ/EKlzMlOs/0Cyrmp7
UYyUgYFQe5nq37Z94rOUSeMgv/WRxaMwrLlLqD78cuF9DSkXaZIX/kECtVaUnjk8
BZx0QhoIHOpQocJUSlm/dLeMuE0+0A3HNR6JVktGsUdv9ulmKw==
-----END CERTIFICATE-----
# certificate for ap-south-2 # certificate for ap-south-2
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAXjwLj9CMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT MIICMzCCAZygAwIBAgIGAXjwLj9CMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
@ -145,6 +273,48 @@ ETwUZ9mTq2vxlV0KvuetCDNS5u4cJsxe/TGGbYP0yP2qfMl0cCImzRI5W0gn8gog
dervfeT7nH5ih0TWEy/QDWfkQ601L4erm4yh4YQq8vcqAPSkf04N dervfeT7nH5ih0TWEy/QDWfkQ601L4erm4yh4YQq8vcqAPSkf04N
-----END CERTIFICATE----- -----END CERTIFICATE-----
# certificate for ap-southeast-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUSqP6ih+++5KF07NXngrWf26mhSUwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE0MzAxNFoXDTI5MDQyODE0MzAxNFowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUSqP6ih++
+5KF07NXngrWf26mhSUwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQAw13BxW11U/JL58j//Fmk7qqtrZTqXmaz1qm2WlIpJpW750MOcP4ux1uPy
eM0RdVZ4jHSMv5gtLAv/PjExBfw9n6vNCk+5GZG4Xec5DoapBZHXmfMo93sjxBFP
4x9rWn0GuwAVO9ukjYPevq2Rerilrq5VvppHtbATVNY2qecXDA==
-----END CERTIFICATE-----
# certificate for ap-southeast-2
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUFxWyAdk4oiXIOC9PxcgjYYh71mwwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE1MjE0M1oXDTI5MDQyODE1MjE0M1owXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUFxWyAdk4
oiXIOC9PxcgjYYh71mwwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQByjeQe6lr7fiIhoGdjBXYzDfkX0lGGvMIhRh57G1bbceQfaYdZd7Ptc0jl
bpycKGaTvhUdkpMOiV2Hi9dOOYawkdhyJDstmDNKu6P9+b6Kak8He5z3NU1tUR2Y
uTwcz7Ye8Nldx//ws3raErfTI7D6s9m63OX8cAJ/f8bNgikwpw==
-----END CERTIFICATE-----
# certificate for ap-southeast-3 # certificate for ap-southeast-3
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAXbVDG2yMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT MIICMzCCAZygAwIBAgIGAXbVDG2yMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
@ -225,23 +395,244 @@ WX00FTEj4hRVjameE1nENoO8Z7fUVloAFDlDo69fhkJeSvn51D1WRrPnoWGgEfr1
+OfK1bAcKTtfkkkP9r4RdwSjKzO5Zu/B+Wqm3kVEz/QNcz6npmA6 +OfK1bAcKTtfkkkP9r4RdwSjKzO5Zu/B+Wqm3kVEz/QNcz6npmA6
-----END CERTIFICATE----- -----END CERTIFICATE-----
# certificate for us-gov-east-1 and us-gov-west-1 # certificate for us-gov-east-1
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDCzCCAnSgAwIBAgIJAIe9Hnq82O7UMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV MIIDITCCAoqgAwIBAgIULVyrqjjwZ461qelPCiShB1KCCj4wDQYJKoZIhvcNAQEL
BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0 BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0yMTA3MTQx BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
NDI3NTdaFw0yNDA3MTMxNDI3NTdaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBX MB4XDTI0MDUwNzE1MjIzNloXDTI5MDUwNjE1MjIzNlowXDELMAkGA1UEBhMCVVMx
YXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6 GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
b24gV2ViIFNlcnZpY2VzIExMQzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
qaIcGFFTx/SO1W5G91jHvyQdGP25n1Y91aXCuOOWAUTvSvNGpXrI4AXNrQF+CmIO A4GNADCBiQKBgQCpohwYUVPH9I7Vbkb3WMe/JB0Y/bmfVj3VpcK445YBRO9K80al
C4beBASnHCx082jYudWBBl9Wiza0psYc9flrczSzVLMmN8w/c78F/95NfiQdnUQP esjgBc2tAX4KYg4Lht4EBKccLHTzaNi51YEGX1aLNrSmxhz1+WtzNLNUsyY3zD9z
pvgqcMeJo82cgHkLR7XoFWgMrZJqrcUK0gnsQcb6kakCAwEAAaOB1DCB0TALBgNV vwX/3k1+JB2dRA+m+Cpwx4mjzZyAeQtHtegVaAytkmqtxQrSCexBxvqRqQIDAQAB
HQ8EBAMCB4AwHQYDVR0OBBYEFNWV53gWJz72F5B1ZVY4O/dfFYBPMIGOBgNVHSME o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQU1ZXneBYnPvYXkHVlVjg7918V
gYYwgYOAFNWV53gWJz72F5B1ZVY4O/dfFYBPoWCkXjBcMQswCQYDVQQGEwJVUzEZ gE8wgZkGA1UdIwSBkTCBjoAU1ZXneBYnPvYXkHVlVjg7918VgE+hYKReMFwxCzAJ
MBcGA1UECBMQV2FzaGluZ3RvbiBTdGF0ZTEQMA4GA1UEBxMHU2VhdHRsZTEgMB4G BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
A1UEChMXQW1hem9uIFdlYiBTZXJ2aWNlcyBMTEOCCQCHvR56vNju1DASBgNVHRMB ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IULVyrqjjw
Af8ECDAGAQH/AgEAMA0GCSqGSIb3DQEBCwUAA4GBACrKjWj460GUPZCGm3/z0dIz Z461qelPCiShB1KCCj4wEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
M2BPuH769wcOsqfFZcMKEysSFK91tVtUb1soFwH4/Lb/T0PqNrvtEwD1Nva5k0h2 AAOBgQBfAL/YZv0y3zmVbXjyxQCsDloeDCJjFKIu3ameEckeIWJbST9LMto0zViZ
xZhNNRmDuhOhW1K9wCcnHGRBwY5t4lYL6hNV6hcrqYwGMjTjcAjBG2yMgznSNFle puIAf05x6GQiEqfBMk+YMxJfcTmJB4Ebaj4egFlslJPSHyC2xuydHlr3B04INOH5
Rwi/S3BFXISixNx9cILu Z2oCM68u6GGbj0jZjg7GJonkReG9N72kDva/ukwZKgq8zErQVQ==
-----END CERTIFICATE----- -----END CERTIFICATE-----
# certificate for us-gov-west-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUe5wGF3jfb7lUHzvDxmM/ktGCLwwwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDUwNzE3MzAzMloXDTI5MDUwNjE3MzAzMlowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCpohwYUVPH9I7Vbkb3WMe/JB0Y/bmfVj3VpcK445YBRO9K80al
esjgBc2tAX4KYg4Lht4EBKccLHTzaNi51YEGX1aLNrSmxhz1+WtzNLNUsyY3zD9z
vwX/3k1+JB2dRA+m+Cpwx4mjzZyAeQtHtegVaAytkmqtxQrSCexBxvqRqQIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQU1ZXneBYnPvYXkHVlVjg7918V
gE8wgZkGA1UdIwSBkTCBjoAU1ZXneBYnPvYXkHVlVjg7918VgE+hYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUe5wGF3jf
b7lUHzvDxmM/ktGCLwwwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQCbTdpx1Iob9SwUReY4exMnlwQlmkTLyA8tYGWzchCJOJJEPfsW0ryy1A0H
YIuvyUty3rJdp9ib8h3GZR71BkZnNddHhy06kPs4p8ewF8+d8OWtOJQcI+ZnFfG4
KyM4rUsBrljpG2aOCm12iACEyrvgJJrS8VZwUDZS6mZEnn/lhA==
-----END CERTIFICATE-----
# certificate for ca-west-1
-----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAYPou9weMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
AlVTMRkwFwYDVQQIDBBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHDAdTZWF0dGxl
MSAwHgYDVQQKDBdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAgFw0yMjEwMTgwMTM2
MDlaGA8yMjAxMTAxODAxMzYwOVowXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgMEFdh
c2hpbmd0b24gU3RhdGUxEDAOBgNVBAcMB1NlYXR0bGUxIDAeBgNVBAoMF0FtYXpv
biBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK
1kIcG5Q6adBXQM75GldfTSiXl7tn54p10TnspI0ErDdb2B6q2Ji/v4XBVH13ZCMg
qlRHMqV8AWI5iO6gFn2A9sN3AZXTMqwtZeiDdebq3k6Wt7ieYvpXTg0qvgsjQIov
RZWaBDBJy9x8C2hW+w9lMQjFHkJ7Jy/PHCJ69EzebQIDAQABMA0GCSqGSIb3DQEB
BQUAA4GBAGe9Snkz1A6rHBH6/5kDtYvtPYwhx2sXNxztbhkXErFk40Nw5l459NZx
EeudxJBLoCkkSgYjhRcOZ/gvDVtWG7qyb6fAqgoisyAbk8K9LzxSim2S1nmT9vD8
4B/t/VvwQBylc+ej8kRxMH7fquZLp7IXfmtBzyUqu6Dpbne+chG2
-----END CERTIFICATE-----
# certificate for ap-northeast-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIULgwDh7TiDrPPBJwscqDwiBHkEFQwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTEyMjMxMFoXDTI5MDQyODEyMjMxMFowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IULgwDh7Ti
DrPPBJwscqDwiBHkEFQwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBtjAglBde1t4F9EHCZOj4qnY6Gigy07Ou54i+lR77MhbpzE8V28Li9l+YT
QMIn6SzJqU3/fIycIro1OVY1lHmaKYgPGSEZxBenSBHfzwDLRmC9oRp4QMe0BjOC
gepj1lUoiN7OA6PtA+ycNlsP0oJvdBjhvayLiuM3tUfLTrgHbw==
-----END CERTIFICATE-----
# certificate for ap-northeast-2
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUbBSn2UIO6vYk4iNWV0RPxJJtHlgwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTEzMzg0NloXDTI5MDQyODEzMzg0NlowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUbBSn2UIO
6vYk4iNWV0RPxJJtHlgwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQAmjTjalG8MGLqWTC2uYqEM8nzI3px1eo0ArvFRsyqQ3fgmWcQpxExqUqRy
l3+2134Kv8dFab04Gut5wlfRtc2OwPKKicmv/IXGN+9bKFnQFjTqif08NIzrDZch
aFT/uvxrIiM+oN2YsHq66GUhO2+xVRXDXVxM/VObFgPERbJpyA==
-----END CERTIFICATE-----
# certificate for ap-northeast-3
-----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAYPou9weMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
AlVTMRkwFwYDVQQIDBBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHDAdTZWF0dGxl
MSAwHgYDVQQKDBdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAgFw0yMjEwMTgwMTM2
MDlaGA8yMjAxMTAxODAxMzYwOVowXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgMEFdh
c2hpbmd0b24gU3RhdGUxEDAOBgNVBAcMB1NlYXR0bGUxIDAeBgNVBAoMF0FtYXpv
biBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK
1kIcG5Q6adBXQM75GldfTSiXl7tn54p10TnspI0ErDdb2B6q2Ji/v4XBVH13ZCMg
qlRHMqV8AWI5iO6gFn2A9sN3AZXTMqwtZeiDdebq3k6Wt7ieYvpXTg0qvgsjQIov
RZWaBDBJy9x8C2hW+w9lMQjFHkJ7Jy/PHCJ69EzebQIDAQABMA0GCSqGSIb3DQEB
BQUAA4GBAGe9Snkz1A6rHBH6/5kDtYvtPYwhx2sXNxztbhkXErFk40Nw5l459NZx
EeudxJBLoCkkSgYjhRcOZ/gvDVtWG7qyb6fAqgoisyAbk8K9LzxSim2S1nmT9vD8
4B/t/VvwQBylc+ej8kRxMH7fquZLp7IXfmtBzyUqu6Dpbne+chG2
-----END CERTIFICATE-----
# certificate for ca-central-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUIrLgixJJB5C4G8z6pZ5rB0JU2aQwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE1MzU0M1oXDTI5MDQyODE1MzU0M1owXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUIrLgixJJ
B5C4G8z6pZ5rB0JU2aQwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBHiQJmzyFAaSYs8SpiRijIDZW2RIo7qBKb/pI3rqK6yOWDlPuMr6yNI81D
IrKGGftg4Z+2KETYU4x76HSf0s//vfH3QA57qFaAwddhKYy4BhteFQl/Wex3xTlX
LiwI07kwJvJy3mS6UfQ4HcvZy219tY+0iyOWrz/jVxwq7TOkCw==
-----END CERTIFICATE-----
# certificate for eu-west-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUakDaQ1Zqy87Hy9ESXA1pFC116HkwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE2MTgxMFoXDTI5MDQyODE2MTgxMFowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUakDaQ1Zq
y87Hy9ESXA1pFC116HkwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQADIkn/MqaLGPuK5+prZZ5Ox4bBZLPtreO2C7r0pqU2kPM2lVPyYYydkvP0
lgSmmsErGu/oL9JNztDe2oCA+kNy17ehcsf8cw0uP861czNFKCeU8b7FgBbL+sIm
qi33rAq6owWGi/5uEcfCR+JP7W+oSYVir5r/yDmWzx+BVH5S/g==
-----END CERTIFICATE-----
# certificate for eu-west-2
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUCgCV/DPxYNND/swDgEKGiC5I+EwwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE2MjkxNFoXDTI5MDQyODE2MjkxNFowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUCgCV/DPx
YNND/swDgEKGiC5I+EwwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQATPu/sOE2esNa4+XPEGKlEJSgqzyBSQLQc+VWo6FAJhGG9fp7D97jhHeLC
5vwfmtTAfnGBxadfAOT3ASkxnOZhXtnRna460LtnNHm7ArCVgXKJo7uBn6ViXtFh
uEEw4y6p9YaLQna+VC8Xtgw6WKq2JXuKzuhuNKSFaGGw9vRcHg==
-----END CERTIFICATE-----
# certificate for eu-west-3
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUaC9fX57UDr6u1vBvsCsECKBZQyIwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE2MzczOFoXDTI5MDQyODE2MzczOFowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUaC9fX57U
Dr6u1vBvsCsECKBZQyIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQCARv1bQEDaMEzYI0nPlu8GHcMXgmgA94HyrXhMMcaIlQwocGBs6VILGVhM
TXP2r3JFaPEpmXSQNQHvGA13clKwAZbni8wtzv6qXb4L4muF34iQRHF0nYrEDoK7
mMPR8+oXKKuPO/mv/XKo6XAV5DDERdSYHX5kkA2R9wtvyZjPnQ==
-----END CERTIFICATE-----
# certificate for eu-north-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUN1c9U6U/xiVDFgJcYKZB4NkH1QEwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE2MDYwM1oXDTI5MDQyODE2MDYwM1owXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUN1c9U6U/
xiVDFgJcYKZB4NkH1QEwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBTIQdoFSDRHkpqNPUbZ9WXR2O5v/9bpmHojMYZb3Hw46wsaRso7STiGGX/
tRqjIkPUIXsdhZ3+7S/RmhFznmZc8e0bjU4n5vi9CJtQSt+1u4E17+V2bF+D3h/7
wcfE0l3414Q8JaTDtfEf/aF3F0uyBvr4MDMd7mFvAMmDmBPSlA==
-----END CERTIFICATE-----
# certificate for sa-east-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUX4Bh4MQ86Roh37VDRRX1MNOB3TcwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE2NDYwOVoXDTI5MDQyODE2NDYwOVowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUX4Bh4MQ8
6Roh37VDRRX1MNOB3TcwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBnhocfH6ZIX6F5K9+Y9V4HFk8vSaaKL5ytw/P5td1h9ej94KF3xkZ5fyjN
URvGQv3kNmNJBoNarcP9I7JIMjsNPmVzqWawyCEGCZImoARxSS3Fc5EAs2PyBfcD
9nCtzMTaKO09Xyq0wqXVYn1xJsE5d5yBDsGrzaTHKjxo61+ezQ==
-----END CERTIFICATE-----

@ -896,5 +896,5 @@ func TestAWS_HardcodedCertificates(t *testing.T) {
assert.True(t, cert.NotAfter.After(time.Now())) assert.True(t, cert.NotAfter.After(time.Now()))
certs = append(certs, cert) certs = append(certs, cert)
} }
assert.Len(t, 14, certs, "expected 14 certificates in aws_certificates.pem") assert.Len(t, 33, certs, "expected 33 certificates in aws_certificates.pem, but got %d", len(certs))
} }

@ -234,24 +234,24 @@ func (c *Claimer) IsSSHCAEnabled() bool {
// Validate validates and modifies the Claims with default values. // Validate validates and modifies the Claims with default values.
func (c *Claimer) Validate() error { func (c *Claimer) Validate() error {
var ( var (
min = c.MinTLSCertDuration() minDur = c.MinTLSCertDuration()
max = c.MaxTLSCertDuration() maxDur = c.MaxTLSCertDuration()
def = c.DefaultTLSCertDuration() defDur = c.DefaultTLSCertDuration()
) )
switch { switch {
case min <= 0: case minDur <= 0:
return errors.Errorf("claims: MinTLSCertDuration must be greater than 0") return errors.Errorf("claims: MinTLSCertDuration must be greater than 0")
case max <= 0: case maxDur <= 0:
return errors.Errorf("claims: MaxTLSCertDuration must be greater than 0") return errors.Errorf("claims: MaxTLSCertDuration must be greater than 0")
case def <= 0: case defDur <= 0:
return errors.Errorf("claims: DefaultTLSCertDuration must be greater than 0") return errors.Errorf("claims: DefaultTLSCertDuration must be greater than 0")
case max < min: case maxDur < minDur:
return errors.Errorf("claims: MaxCertDuration cannot be less "+ return errors.Errorf("claims: MaxCertDuration cannot be less "+
"than MinCertDuration: MaxCertDuration - %v, MinCertDuration - %v", max, min) "than MinCertDuration: MaxCertDuration - %v, MinCertDuration - %v", maxDur, minDur)
case def < min: case defDur < minDur:
return errors.Errorf("claims: DefaultCertDuration cannot be less than MinCertDuration: DefaultCertDuration - %v, MinCertDuration - %v", def, min) return errors.Errorf("claims: DefaultCertDuration cannot be less than MinCertDuration: DefaultCertDuration - %v, MinCertDuration - %v", defDur, minDur)
case max < def: case maxDur < defDur:
return errors.Errorf("claims: MaxCertDuration cannot be less than DefaultCertDuration: MaxCertDuration - %v, DefaultCertDuration - %v", max, def) return errors.Errorf("claims: MaxCertDuration cannot be less than DefaultCertDuration: MaxCertDuration - %v, DefaultCertDuration - %v", maxDur, defDur)
default: default:
return nil return nil
} }

@ -125,7 +125,7 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
} }
// Try with azp (OIDC) // Try with azp (OIDC)
if len(payload.AuthorizedParty) > 0 { if payload.AuthorizedParty != "" {
if p, ok := c.LoadByTokenID(payload.AuthorizedParty); ok { if p, ok := c.LoadByTokenID(payload.AuthorizedParty); ok {
return p, ok return p, ok
} }

@ -87,7 +87,7 @@ func (p *JWK) GetType() Type {
// GetEncryptedKey returns the base provisioner encrypted key if it's defined. // GetEncryptedKey returns the base provisioner encrypted key if it's defined.
func (p *JWK) GetEncryptedKey() (string, string, bool) { func (p *JWK) GetEncryptedKey() (string, string, bool) {
return p.Key.KeyID, p.EncryptedKey, len(p.EncryptedKey) > 0 return p.Key.KeyID, p.EncryptedKey, p.EncryptedKey != ""
} }
// Init initializes and validates the fields of a JWK type. // Init initializes and validates the fields of a JWK type.

@ -105,7 +105,7 @@ func getKeysFromJWKsURI(uri string) (jose.JSONWebKeySet, time.Duration, error) {
func getCacheAge(cacheControl string) time.Duration { func getCacheAge(cacheControl string) time.Duration {
age := defaultCacheAge age := defaultCacheAge
if len(cacheControl) > 0 { if cacheControl != "" {
match := maxAgeRegex.FindAllStringSubmatch(cacheControl, -1) match := maxAgeRegex.FindAllStringSubmatch(cacheControl, -1)
if len(match) > 0 { if len(match) > 0 {
if len(match[0]) == 2 { if len(match[0]) == 2 {

@ -93,6 +93,8 @@ type OIDC struct {
ListenAddress string `json:"listenAddress,omitempty"` ListenAddress string `json:"listenAddress,omitempty"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"` Options *Options `json:"options,omitempty"`
Scopes []string `json:"scopes,omitempty"`
AuthParams []string `json:"authParams,omitempty"`
configuration openIDConfiguration configuration openIDConfiguration
keyStore *keyStore keyStore *keyStore
ctl *Controller ctl *Controller

@ -10,6 +10,7 @@ import (
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
kmsapi "go.step.sm/crypto/kms/apiv1"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
@ -206,6 +207,13 @@ type SSHKeys struct {
HostKeys []ssh.PublicKey HostKeys []ssh.PublicKey
} }
// SCEPKeyManager is a KMS interface that combines a KeyManager with a
// Decrypter.
type SCEPKeyManager interface {
kmsapi.KeyManager
kmsapi.Decrypter
}
// Config defines the default parameters used in the initialization of // Config defines the default parameters used in the initialization of
// provisioners. // provisioners.
type Config struct { type Config struct {
@ -226,6 +234,8 @@ type Config struct {
AuthorizeSSHRenewFunc AuthorizeSSHRenewFunc AuthorizeSSHRenewFunc AuthorizeSSHRenewFunc
// WebhookClient is an http client to use in webhook request // WebhookClient is an http client to use in webhook request
WebhookClient *http.Client WebhookClient *http.Client
// SCEPKeyManager, if defined, is the interface used by SCEP provisioners.
SCEPKeyManager SCEPKeyManager
} }
type provisioner struct { type provisioner struct {
@ -320,7 +330,7 @@ func (b *base) AuthorizeSSHSign(context.Context, string) ([]SignOption, error) {
return nil, errs.Unauthorized("provisioner.AuthorizeSSHSign not implemented") return nil, errs.Unauthorized("provisioner.AuthorizeSSHSign not implemented")
} }
// AuthorizeRevoke returns an unimplemented error. Provisioners should overwrite // AuthorizeSSHRevoke returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for revoking SSH Certificates. // this method if they will support authorizing tokens for revoking SSH Certificates.
func (b *base) AuthorizeSSHRevoke(context.Context, string) error { func (b *base) AuthorizeSSHRevoke(context.Context, string) error {
return errs.Unauthorized("provisioner.AuthorizeSSHRevoke not implemented") return errs.Unauthorized("provisioner.AuthorizeSSHRevoke not implemented")

@ -15,7 +15,6 @@ import (
"go.step.sm/crypto/kms" "go.step.sm/crypto/kms"
kmsapi "go.step.sm/crypto/kms/apiv1" kmsapi "go.step.sm/crypto/kms/apiv1"
"go.step.sm/crypto/kms/uri"
"go.step.sm/linkedca" "go.step.sm/linkedca"
"github.com/smallstep/certificates/webhook" "github.com/smallstep/certificates/webhook"
@ -59,7 +58,7 @@ type SCEP struct {
encryptionAlgorithm int encryptionAlgorithm int
challengeValidationController *challengeValidationController challengeValidationController *challengeValidationController
notificationController *notificationController notificationController *notificationController
keyManager kmsapi.KeyManager keyManager SCEPKeyManager
decrypter crypto.Decrypter decrypter crypto.Decrypter
decrypterCertificate *x509.Certificate decrypterCertificate *x509.Certificate
signer crypto.Signer signer crypto.Signer
@ -269,34 +268,38 @@ func (s *SCEP) Init(config Config) (err error) {
) )
// parse the decrypter key PEM contents if available // parse the decrypter key PEM contents if available
if decryptionKeyPEM := s.DecrypterKeyPEM; len(decryptionKeyPEM) > 0 { if len(s.DecrypterKeyPEM) > 0 {
// try reading the PEM for validation // try reading the PEM for validation
block, rest := pem.Decode(decryptionKeyPEM) block, rest := pem.Decode(s.DecrypterKeyPEM)
if len(rest) > 0 { if len(rest) > 0 {
return errors.New("failed parsing decrypter key: trailing data") return errors.New("failed parsing decrypter key: trailing data")
} }
if block == nil { if block == nil {
return errors.New("failed parsing decrypter key: no PEM block found") return errors.New("failed parsing decrypter key: no PEM block found")
} }
opts := kms.Options{ opts := kms.Options{
Type: kmsapi.SoftKMS, Type: kmsapi.SoftKMS,
} }
if s.keyManager, err = kms.New(context.Background(), opts); err != nil { km, err := kms.New(context.Background(), opts)
if err != nil {
return fmt.Errorf("failed initializing kms: %w", err) return fmt.Errorf("failed initializing kms: %w", err)
} }
kmsDecrypter, ok := s.keyManager.(kmsapi.Decrypter) scepKeyManager, ok := km.(SCEPKeyManager)
if !ok { if !ok {
return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type) return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type)
} }
if s.decrypter, err = kmsDecrypter.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ s.keyManager = scepKeyManager
DecryptionKeyPEM: decryptionKeyPEM,
if s.decrypter, err = s.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKeyPEM: s.DecrypterKeyPEM,
Password: []byte(s.DecrypterKeyPassword), Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter, PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil { }); err != nil {
return fmt.Errorf("failed creating decrypter: %w", err) return fmt.Errorf("failed creating decrypter: %w", err)
} }
if s.signer, err = s.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ if s.signer, err = s.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKeyPEM: decryptionKeyPEM, // TODO(hs): support distinct signer key in the future? SigningKeyPEM: s.DecrypterKeyPEM, // TODO(hs): support distinct signer key in the future?
Password: []byte(s.DecrypterKeyPassword), Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter, PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil { }); err != nil {
@ -304,41 +307,44 @@ func (s *SCEP) Init(config Config) (err error) {
} }
} }
if decryptionKeyURI := s.DecrypterKeyURI; len(decryptionKeyURI) > 0 { if s.DecrypterKeyURI != "" {
u, err := uri.Parse(s.DecrypterKeyURI) kmsType, err := kmsapi.TypeOf(s.DecrypterKeyURI)
if err != nil { if err != nil {
return fmt.Errorf("failed parsing decrypter key: %w", err) return fmt.Errorf("failed parsing decrypter key: %w", err)
} }
var kmsType kmsapi.Type
switch { if config.SCEPKeyManager != nil {
case u.Scheme != "": s.keyManager = config.SCEPKeyManager
kmsType = kms.Type(u.Scheme) } else {
default: if kmsType == kmsapi.DefaultKMS {
kmsType = kmsapi.SoftKMS kmsType = kmsapi.SoftKMS
} }
opts := kms.Options{ opts := kms.Options{
Type: kmsType, Type: kmsType,
URI: s.DecrypterKeyURI, URI: s.DecrypterKeyURI,
} }
if s.keyManager, err = kms.New(context.Background(), opts); err != nil { km, err := kms.New(context.Background(), opts)
return fmt.Errorf("failed initializing kms: %w", err) if err != nil {
} return fmt.Errorf("failed initializing kms: %w", err)
kmsDecrypter, ok := s.keyManager.(kmsapi.Decrypter) }
if !ok { scepKeyManager, ok := km.(SCEPKeyManager)
return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type) if !ok {
} return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type)
if kmsType != "softkms" { // TODO(hs): this should likely become more transparent? }
decryptionKeyURI = u.Opaque s.keyManager = scepKeyManager
} }
if s.decrypter, err = kmsDecrypter.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: decryptionKeyURI, // Create decrypter and signer with the same key:
// TODO(hs): support distinct signer key in the future?
if s.decrypter, err = s.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: s.DecrypterKeyURI,
Password: []byte(s.DecrypterKeyPassword), Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter, PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil { }); err != nil {
return fmt.Errorf("failed creating decrypter: %w", err) return fmt.Errorf("failed creating decrypter: %w", err)
} }
if s.signer, err = s.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ if s.signer, err = s.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: decryptionKeyURI, // TODO(hs): support distinct signer key in the future? SigningKey: s.DecrypterKeyURI,
Password: []byte(s.DecrypterKeyPassword), Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter, PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil { }); err != nil {

@ -2,19 +2,27 @@ package provisioner
import ( import (
"context" "context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/json" "encoding/json"
"encoding/pem"
"errors" "errors"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"path/filepath"
"testing" "testing"
"github.com/smallstep/certificates/webhook"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.step.sm/crypto/kms/softkms"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/pemutil"
"go.step.sm/linkedca" "go.step.sm/linkedca"
"github.com/smallstep/certificates/webhook"
) )
func Test_challengeValidationController_Validate(t *testing.T) { func Test_challengeValidationController_Validate(t *testing.T) {
@ -366,3 +374,270 @@ func TestSCEP_ValidateChallenge(t *testing.T) {
}) })
} }
} }
func TestSCEP_Init(t *testing.T) {
serialize := func(key crypto.PrivateKey, password string) []byte {
var opts []pemutil.Options
if password == "" {
opts = append(opts, pemutil.WithPasswordPrompt("no password", func(s string) ([]byte, error) {
return nil, nil
}))
} else {
opts = append(opts, pemutil.WithPassword([]byte("password")))
}
block, err := pemutil.Serialize(key, opts...)
require.NoError(t, err)
return pem.EncodeToMemory(block)
}
ca, err := minica.New()
require.NoError(t, err)
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
badKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
cert, err := ca.Sign(&x509.Certificate{
Subject: pkix.Name{CommonName: "SCEP decryptor"},
PublicKey: key.Public(),
})
require.NoError(t, err)
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: cert.Raw,
})
certPEMWithIntermediate := append(pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: cert.Raw,
}), pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: ca.Intermediate.Raw,
})...)
keyPEM := serialize(key, "password")
keyPEMNoPassword := serialize(key, "")
badKeyPEM := serialize(badKey, "password")
tmp := t.TempDir()
path := filepath.Join(tmp, "rsa.priv")
pathNoPassword := filepath.Join(tmp, "rsa.key")
require.NoError(t, os.WriteFile(path, keyPEM, 0600))
require.NoError(t, os.WriteFile(pathNoPassword, keyPEMNoPassword, 0600))
type args struct {
config Config
}
tests := []struct {
name string
s *SCEP
args args
wantErr bool
}{
{"ok", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok no password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEMNoPassword,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 1,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok with uri", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 1024,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + path,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 2,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok with uri no password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 2048,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + pathNoPassword,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 3,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok with SCEPKeyManager", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 2048,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + pathNoPassword,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 4,
}, args{Config{Claims: globalProvisionerClaims, SCEPKeyManager: &softkms.SoftKMS{}}}, false},
{"ok intermediate", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: nil,
DecrypterKeyPEM: nil,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"fail type", &SCEP{
Type: "",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail name", &SCEP{
Type: "SCEP",
Name: "",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail minimumPublicKeyLength", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 2001,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail encryptionAlgorithmIdentifier", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 5,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail negative encryptionAlgorithmIdentifier", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: -1,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail key decode", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: []byte("not a pem"),
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail certificate decode", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: []byte("not a pem"),
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail certificate with intermediate", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEMWithIntermediate,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail decrypter password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "badpassword",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail uri", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=missing.key",
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail uri password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + path,
DecrypterKeyPassword: "badpassword",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail uri type", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "foo:path=" + path,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail missing certificate", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: nil,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail key match", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: badKeyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.s.Init(tt.args.config); (err != nil) != tt.wantErr {
t.Errorf("SCEP.Init() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

@ -369,8 +369,8 @@ type validityValidator struct {
} }
// newValidityValidator return a new validity validator. // newValidityValidator return a new validity validator.
func newValidityValidator(min, max time.Duration) *validityValidator { func newValidityValidator(minDur, maxDur time.Duration) *validityValidator {
return &validityValidator{min: min, max: max} return &validityValidator{min: minDur, max: maxDur}
} }
// Valid validates the certificate validity settings (notBefore/notAfter) and // Valid validates the certificate validity settings (notBefore/notAfter) and

@ -276,14 +276,14 @@ func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOpti
return errs.BadRequest("ssh certificate validBefore cannot be before validAfter") return errs.BadRequest("ssh certificate validBefore cannot be before validAfter")
} }
var min, max time.Duration var minDur, maxDur time.Duration
switch cert.CertType { switch cert.CertType {
case ssh.UserCert: case ssh.UserCert:
min = v.MinUserSSHCertDuration() minDur = v.MinUserSSHCertDuration()
max = v.MaxUserSSHCertDuration() maxDur = v.MaxUserSSHCertDuration()
case ssh.HostCert: case ssh.HostCert:
min = v.MinHostSSHCertDuration() minDur = v.MinHostSSHCertDuration()
max = v.MaxHostSSHCertDuration() maxDur = v.MaxHostSSHCertDuration()
case 0: case 0:
return errs.BadRequest("ssh certificate type has not been set") return errs.BadRequest("ssh certificate type has not been set")
default: default:
@ -295,10 +295,10 @@ func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOpti
dur := time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second dur := time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second
switch { switch {
case dur < min: case dur < minDur:
return errs.Forbidden("requested duration of %s is less than minimum accepted duration for selected provisioner of %s", dur, min) return errs.Forbidden("requested duration of %s is less than minimum accepted duration for selected provisioner of %s", dur, minDur)
case dur > max+opts.Backdate: case dur > maxDur+opts.Backdate:
return errs.Forbidden("requested duration of %s is greater than maximum accepted duration for selected provisioner of %s", dur, max+opts.Backdate) return errs.Forbidden("requested duration of %s is greater than maximum accepted duration for selected provisioner of %s", dur, maxDur+opts.Backdate)
default: default:
return nil return nil
} }

@ -15,6 +15,7 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/middleware/requestid"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
"github.com/smallstep/certificates/webhook" "github.com/smallstep/certificates/webhook"
"go.step.sm/linkedca" "go.step.sm/linkedca"
@ -36,7 +37,7 @@ type WebhookController struct {
// Enrich fetches data from remote servers and adds returned data to the // Enrich fetches data from remote servers and adds returned data to the
// templateData // templateData
func (wc *WebhookController) Enrich(req *webhook.RequestBody) error { func (wc *WebhookController) Enrich(ctx context.Context, req *webhook.RequestBody) error {
if wc == nil { if wc == nil {
return nil return nil
} }
@ -55,7 +56,11 @@ func (wc *WebhookController) Enrich(req *webhook.RequestBody) error {
if !wc.isCertTypeOK(wh) { if !wc.isCertTypeOK(wh) {
continue continue
} }
resp, err := wh.Do(wc.client, req, wc.TemplateData)
whCtx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel() //nolint:gocritic // every request canceled with its own timeout
resp, err := wh.DoWithContext(whCtx, wc.client, req, wc.TemplateData)
if err != nil { if err != nil {
return err return err
} }
@ -68,7 +73,7 @@ func (wc *WebhookController) Enrich(req *webhook.RequestBody) error {
} }
// Authorize checks that all remote servers allow the request // Authorize checks that all remote servers allow the request
func (wc *WebhookController) Authorize(req *webhook.RequestBody) error { func (wc *WebhookController) Authorize(ctx context.Context, req *webhook.RequestBody) error {
if wc == nil { if wc == nil {
return nil return nil
} }
@ -87,7 +92,11 @@ func (wc *WebhookController) Authorize(req *webhook.RequestBody) error {
if !wc.isCertTypeOK(wh) { if !wc.isCertTypeOK(wh) {
continue continue
} }
resp, err := wh.Do(wc.client, req, wc.TemplateData)
whCtx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel() //nolint:gocritic // every request canceled with its own timeout
resp, err := wh.DoWithContext(whCtx, wc.client, req, wc.TemplateData)
if err != nil { if err != nil {
return err return err
} }
@ -123,13 +132,6 @@ type Webhook struct {
} `json:"-"` } `json:"-"`
} }
func (w *Webhook) Do(client *http.Client, reqBody *webhook.RequestBody, data any) (*webhook.ResponseBody, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
return w.DoWithContext(ctx, client, reqBody, data)
}
func (w *Webhook) DoWithContext(ctx context.Context, client *http.Client, reqBody *webhook.RequestBody, data any) (*webhook.ResponseBody, error) { func (w *Webhook) DoWithContext(ctx context.Context, client *http.Client, reqBody *webhook.RequestBody, data any) (*webhook.ResponseBody, error) {
tmpl, err := template.New("url").Funcs(templates.StepFuncMap()).Parse(w.URL) tmpl, err := template.New("url").Funcs(templates.StepFuncMap()).Parse(w.URL)
if err != nil { if err != nil {
@ -169,6 +171,10 @@ retry:
return nil, err return nil, err
} }
if requestID, ok := requestid.FromContext(ctx); ok {
req.Header.Set("X-Request-Id", requestID)
}
secret, err := base64.StdEncoding.DecodeString(w.Secret) secret, err := base64.StdEncoding.DecodeString(w.Secret)
if err != nil { if err != nil {
return nil, err return nil, err

@ -1,6 +1,7 @@
package provisioner package provisioner
import ( import (
"context"
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"crypto/tls" "crypto/tls"
@ -8,18 +9,23 @@ import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pkg/errors"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/webhook"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
"go.step.sm/linkedca" "go.step.sm/linkedca"
"github.com/smallstep/certificates/middleware/requestid"
"github.com/smallstep/certificates/webhook"
) )
func TestWebhookController_isCertTypeOK(t *testing.T) { func TestWebhookController_isCertTypeOK(t *testing.T) {
@ -92,19 +98,25 @@ func TestWebhookController_isCertTypeOK(t *testing.T) {
} }
for name, test := range tests { for name, test := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert.Equals(t, test.want, test.wc.isCertTypeOK(test.wh)) assert.Equal(t, test.want, test.wc.isCertTypeOK(test.wh))
}) })
} }
} }
// withRequestID is a helper that calls into [requestid.NewContext] and returns
// a new context with the requestID added.
func withRequestID(t *testing.T, ctx context.Context, requestID string) context.Context {
t.Helper()
return requestid.NewContext(ctx, requestID)
}
func TestWebhookController_Enrich(t *testing.T) { func TestWebhookController_Enrich(t *testing.T) {
cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock()) cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock())
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
type test struct { type test struct {
ctl *WebhookController ctl *WebhookController
ctx context.Context
req *webhook.RequestBody req *webhook.RequestBody
responses []*webhook.ResponseBody responses []*webhook.ResponseBody
expectErr bool expectErr bool
@ -129,6 +141,7 @@ func TestWebhookController_Enrich(t *testing.T) {
webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}}, webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}},
TemplateData: x509util.TemplateData{}, TemplateData: x509util.TemplateData{},
}, },
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{}, req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true, Data: map[string]any{"role": "bar"}}}, responses: []*webhook.ResponseBody{{Allow: true, Data: map[string]any{"role": "bar"}}},
expectErr: false, expectErr: false,
@ -143,6 +156,7 @@ func TestWebhookController_Enrich(t *testing.T) {
}, },
TemplateData: x509util.TemplateData{}, TemplateData: x509util.TemplateData{},
}, },
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{}, req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{ responses: []*webhook.ResponseBody{
{Allow: true, Data: map[string]any{"role": "bar"}}, {Allow: true, Data: map[string]any{"role": "bar"}},
@ -166,6 +180,7 @@ func TestWebhookController_Enrich(t *testing.T) {
TemplateData: x509util.TemplateData{}, TemplateData: x509util.TemplateData{},
certType: linkedca.Webhook_X509, certType: linkedca.Webhook_X509,
}, },
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{}, req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{ responses: []*webhook.ResponseBody{
{Allow: true, Data: map[string]any{"role": "bar"}}, {Allow: true, Data: map[string]any{"role": "bar"}},
@ -185,14 +200,15 @@ func TestWebhookController_Enrich(t *testing.T) {
TemplateData: x509util.TemplateData{}, TemplateData: x509util.TemplateData{},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)}, options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)},
}, },
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{}, req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true, Data: map[string]any{"role": "bar"}}}, responses: []*webhook.ResponseBody{{Allow: true, Data: map[string]any{"role": "bar"}}},
expectErr: false, expectErr: false,
expectTemplateData: x509util.TemplateData{"Webhooks": map[string]any{"people": map[string]any{"role": "bar"}}}, expectTemplateData: x509util.TemplateData{"Webhooks": map[string]any{"people": map[string]any{"role": "bar"}}},
assertRequest: func(t *testing.T, req *webhook.RequestBody) { assertRequest: func(t *testing.T, req *webhook.RequestBody) {
key, err := x509.MarshalPKIXPublicKey(cert.PublicKey) key, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
assert.FatalError(t, err) require.NoError(t, err)
assert.Equals(t, &webhook.X5CCertificate{ assert.Equal(t, &webhook.X5CCertificate{
Raw: cert.Raw, Raw: cert.Raw,
PublicKey: key, PublicKey: key,
PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(), PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(),
@ -207,6 +223,7 @@ func TestWebhookController_Enrich(t *testing.T) {
webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}}, webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}},
TemplateData: x509util.TemplateData{}, TemplateData: x509util.TemplateData{},
}, },
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{}, req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}}, responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true, expectErr: true,
@ -221,6 +238,7 @@ func TestWebhookController_Enrich(t *testing.T) {
PublicKey: []byte("bad"), PublicKey: []byte("bad"),
})}, })},
}, },
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{}, req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}}, responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true, expectErr: true,
@ -232,19 +250,21 @@ func TestWebhookController_Enrich(t *testing.T) {
for i, wh := range test.ctl.webhooks { for i, wh := range test.ctl.webhooks {
var j = i var j = i
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "reqID", r.Header.Get("X-Request-ID"))
err := json.NewEncoder(w).Encode(test.responses[j]) err := json.NewEncoder(w).Encode(test.responses[j])
assert.FatalError(t, err) require.NoError(t, err)
})) }))
// nolint: gocritic // defer in loop isn't a memory leak // nolint: gocritic // defer in loop isn't a memory leak
defer ts.Close() defer ts.Close()
wh.URL = ts.URL wh.URL = ts.URL
} }
err := test.ctl.Enrich(test.req) err := test.ctl.Enrich(test.ctx, test.req)
if (err != nil) != test.expectErr { if (err != nil) != test.expectErr {
t.Fatalf("Got err %v, want %v", err, test.expectErr) t.Fatalf("Got err %v, want %v", err, test.expectErr)
} }
assert.Equals(t, test.expectTemplateData, test.ctl.TemplateData) assert.Equal(t, test.expectTemplateData, test.ctl.TemplateData)
if test.assertRequest != nil { if test.assertRequest != nil {
test.assertRequest(t, test.req) test.assertRequest(t, test.req)
} }
@ -254,12 +274,11 @@ func TestWebhookController_Enrich(t *testing.T) {
func TestWebhookController_Authorize(t *testing.T) { func TestWebhookController_Authorize(t *testing.T) {
cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock()) cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock())
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
type test struct { type test struct {
ctl *WebhookController ctl *WebhookController
ctx context.Context
req *webhook.RequestBody req *webhook.RequestBody
responses []*webhook.ResponseBody responses []*webhook.ResponseBody
expectErr bool expectErr bool
@ -280,6 +299,7 @@ func TestWebhookController_Authorize(t *testing.T) {
client: http.DefaultClient, client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}}, webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}},
}, },
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{}, req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true}}, responses: []*webhook.ResponseBody{{Allow: true}},
expectErr: false, expectErr: false,
@ -290,6 +310,7 @@ func TestWebhookController_Authorize(t *testing.T) {
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING", CertType: linkedca.Webhook_X509.String()}}, webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING", CertType: linkedca.Webhook_X509.String()}},
certType: linkedca.Webhook_SSH, certType: linkedca.Webhook_SSH,
}, },
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{}, req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}}, responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: false, expectErr: false,
@ -300,13 +321,14 @@ func TestWebhookController_Authorize(t *testing.T) {
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}}, webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)}, options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)},
}, },
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{}, req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true}}, responses: []*webhook.ResponseBody{{Allow: true}},
expectErr: false, expectErr: false,
assertRequest: func(t *testing.T, req *webhook.RequestBody) { assertRequest: func(t *testing.T, req *webhook.RequestBody) {
key, err := x509.MarshalPKIXPublicKey(cert.PublicKey) key, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
assert.FatalError(t, err) require.NoError(t, err)
assert.Equals(t, &webhook.X5CCertificate{ assert.Equal(t, &webhook.X5CCertificate{
Raw: cert.Raw, Raw: cert.Raw,
PublicKey: key, PublicKey: key,
PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(), PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(),
@ -320,6 +342,7 @@ func TestWebhookController_Authorize(t *testing.T) {
client: http.DefaultClient, client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}}, webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}},
}, },
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{}, req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}}, responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true, expectErr: true,
@ -332,6 +355,7 @@ func TestWebhookController_Authorize(t *testing.T) {
PublicKey: []byte("bad"), PublicKey: []byte("bad"),
})}, })},
}, },
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{}, req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}}, responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true, expectErr: true,
@ -342,15 +366,17 @@ func TestWebhookController_Authorize(t *testing.T) {
for i, wh := range test.ctl.webhooks { for i, wh := range test.ctl.webhooks {
var j = i var j = i
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "reqID", r.Header.Get("X-Request-ID"))
err := json.NewEncoder(w).Encode(test.responses[j]) err := json.NewEncoder(w).Encode(test.responses[j])
assert.FatalError(t, err) require.NoError(t, err)
})) }))
// nolint: gocritic // defer in loop isn't a memory leak // nolint: gocritic // defer in loop isn't a memory leak
defer ts.Close() defer ts.Close()
wh.URL = ts.URL wh.URL = ts.URL
} }
err := test.ctl.Authorize(test.req) err := test.ctl.Authorize(test.ctx, test.req)
if (err != nil) != test.expectErr { if (err != nil) != test.expectErr {
t.Fatalf("Got err %v, want %v", err, test.expectErr) t.Fatalf("Got err %v, want %v", err, test.expectErr)
} }
@ -366,6 +392,7 @@ func TestWebhook_Do(t *testing.T) {
type test struct { type test struct {
webhook Webhook webhook Webhook
dataArg any dataArg any
requestID string
webhookResponse webhook.ResponseBody webhookResponse webhook.ResponseBody
expectPath string expectPath string
errStatusCode int errStatusCode int
@ -375,6 +402,16 @@ func TestWebhook_Do(t *testing.T) {
} }
tests := map[string]test{ tests := map[string]test{
"ok": { "ok": {
webhook: Webhook{
ID: "abc123",
Secret: "c2VjcmV0Cg==",
},
requestID: "reqID",
webhookResponse: webhook.ResponseBody{
Data: map[string]interface{}{"role": "dba"},
},
},
"ok/no-request-id": {
webhook: Webhook{ webhook: Webhook{
ID: "abc123", ID: "abc123",
Secret: "c2VjcmV0Cg==", Secret: "c2VjcmV0Cg==",
@ -389,6 +426,7 @@ func TestWebhook_Do(t *testing.T) {
Secret: "c2VjcmV0Cg==", Secret: "c2VjcmV0Cg==",
BearerToken: "mytoken", BearerToken: "mytoken",
}, },
requestID: "reqID",
webhookResponse: webhook.ResponseBody{ webhookResponse: webhook.ResponseBody{
Data: map[string]interface{}{"role": "dba"}, Data: map[string]interface{}{"role": "dba"},
}, },
@ -405,6 +443,7 @@ func TestWebhook_Do(t *testing.T) {
Password: "mypass", Password: "mypass",
}, },
}, },
requestID: "reqID",
webhookResponse: webhook.ResponseBody{ webhookResponse: webhook.ResponseBody{
Data: map[string]interface{}{"role": "dba"}, Data: map[string]interface{}{"role": "dba"},
}, },
@ -416,7 +455,8 @@ func TestWebhook_Do(t *testing.T) {
URL: "/users/{{ .username }}?region={{ .region }}", URL: "/users/{{ .username }}?region={{ .region }}",
Secret: "c2VjcmV0Cg==", Secret: "c2VjcmV0Cg==",
}, },
dataArg: map[string]interface{}{"username": "areed", "region": "central"}, requestID: "reqID",
dataArg: map[string]interface{}{"username": "areed", "region": "central"},
webhookResponse: webhook.ResponseBody{ webhookResponse: webhook.ResponseBody{
Data: map[string]interface{}{"role": "dba"}, Data: map[string]interface{}{"role": "dba"},
}, },
@ -451,6 +491,7 @@ func TestWebhook_Do(t *testing.T) {
ID: "abc123", ID: "abc123",
Secret: "c2VjcmV0Cg==", Secret: "c2VjcmV0Cg==",
}, },
requestID: "reqID",
webhookResponse: webhook.ResponseBody{ webhookResponse: webhook.ResponseBody{
Allow: true, Allow: true,
}, },
@ -463,6 +504,7 @@ func TestWebhook_Do(t *testing.T) {
webhookResponse: webhook.ResponseBody{ webhookResponse: webhook.ResponseBody{
Data: map[string]interface{}{"role": "dba"}, Data: map[string]interface{}{"role": "dba"},
}, },
requestID: "reqID",
errStatusCode: 404, errStatusCode: 404,
serverErrMsg: "item not found", serverErrMsg: "item not found",
expectErr: errors.New("Webhook server responded with 404"), expectErr: errors.New("Webhook server responded with 404"),
@ -471,17 +513,20 @@ func TestWebhook_Do(t *testing.T) {
for name, tc := range tests { for name, tc := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := r.Header.Get("X-Smallstep-Webhook-ID") if tc.requestID != "" {
assert.Equals(t, tc.webhook.ID, id) assert.Equal(t, tc.requestID, r.Header.Get("X-Request-ID"))
}
assert.Equal(t, tc.webhook.ID, r.Header.Get("X-Smallstep-Webhook-ID"))
sig, err := hex.DecodeString(r.Header.Get("X-Smallstep-Signature")) sig, err := hex.DecodeString(r.Header.Get("X-Smallstep-Signature"))
assert.FatalError(t, err) assert.NoError(t, err)
body, err := io.ReadAll(r.Body) body, err := io.ReadAll(r.Body)
assert.FatalError(t, err) assert.NoError(t, err)
secret, err := base64.StdEncoding.DecodeString(tc.webhook.Secret) secret, err := base64.StdEncoding.DecodeString(tc.webhook.Secret)
assert.FatalError(t, err) assert.NoError(t, err)
h := hmac.New(sha256.New, secret) h := hmac.New(sha256.New, secret)
h.Write(body) h.Write(body)
mac := h.Sum(nil) mac := h.Sum(nil)
@ -490,19 +535,19 @@ func TestWebhook_Do(t *testing.T) {
switch { switch {
case tc.webhook.BearerToken != "": case tc.webhook.BearerToken != "":
ah := fmt.Sprintf("Bearer %s", tc.webhook.BearerToken) ah := fmt.Sprintf("Bearer %s", tc.webhook.BearerToken)
assert.Equals(t, ah, r.Header.Get("Authorization")) assert.Equal(t, ah, r.Header.Get("Authorization"))
case tc.webhook.BasicAuth.Username != "" || tc.webhook.BasicAuth.Password != "": case tc.webhook.BasicAuth.Username != "" || tc.webhook.BasicAuth.Password != "":
whReq, err := http.NewRequest("", "", http.NoBody) whReq, err := http.NewRequest("", "", http.NoBody)
assert.FatalError(t, err) require.NoError(t, err)
whReq.SetBasicAuth(tc.webhook.BasicAuth.Username, tc.webhook.BasicAuth.Password) whReq.SetBasicAuth(tc.webhook.BasicAuth.Username, tc.webhook.BasicAuth.Password)
ah := whReq.Header.Get("Authorization") ah := whReq.Header.Get("Authorization")
assert.Equals(t, ah, whReq.Header.Get("Authorization")) assert.Equal(t, ah, whReq.Header.Get("Authorization"))
default: default:
assert.Equals(t, "", r.Header.Get("Authorization")) assert.Equal(t, "", r.Header.Get("Authorization"))
} }
if tc.expectPath != "" { if tc.expectPath != "" {
assert.Equals(t, tc.expectPath, r.URL.Path+"?"+r.URL.RawQuery) assert.Equal(t, tc.expectPath, r.URL.Path+"?"+r.URL.RawQuery)
} }
if tc.errStatusCode != 0 { if tc.errStatusCode != 0 {
@ -512,26 +557,33 @@ func TestWebhook_Do(t *testing.T) {
reqBody := new(webhook.RequestBody) reqBody := new(webhook.RequestBody)
err = json.Unmarshal(body, reqBody) err = json.Unmarshal(body, reqBody)
assert.FatalError(t, err) require.NoError(t, err)
// assert.Equals(t, tc.expectToken, reqBody.Token)
err = json.NewEncoder(w).Encode(tc.webhookResponse) err = json.NewEncoder(w).Encode(tc.webhookResponse)
assert.FatalError(t, err) require.NoError(t, err)
})) }))
defer ts.Close() defer ts.Close()
tc.webhook.URL = ts.URL + tc.webhook.URL tc.webhook.URL = ts.URL + tc.webhook.URL
reqBody, err := webhook.NewRequestBody(webhook.WithX509CertificateRequest(csr)) reqBody, err := webhook.NewRequestBody(webhook.WithX509CertificateRequest(csr))
assert.FatalError(t, err) require.NoError(t, err)
got, err := tc.webhook.Do(http.DefaultClient, reqBody, tc.dataArg)
ctx := context.Background()
if tc.requestID != "" {
ctx = withRequestID(t, ctx, tc.requestID)
}
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
got, err := tc.webhook.DoWithContext(ctx, http.DefaultClient, reqBody, tc.dataArg)
if tc.expectErr != nil { if tc.expectErr != nil {
assert.Equals(t, tc.expectErr.Error(), err.Error()) assert.Equal(t, tc.expectErr.Error(), err.Error())
return return
} }
assert.FatalError(t, err) assert.NoError(t, err)
assert.Equals(t, got, &tc.webhookResponse) assert.Equal(t, &tc.webhookResponse, got)
}) })
} }
@ -544,7 +596,7 @@ func TestWebhook_Do(t *testing.T) {
URL: ts.URL, URL: ts.URL,
} }
cert, err := tls.LoadX509KeyPair("testdata/certs/foo.crt", "testdata/secrets/foo.key") cert, err := tls.LoadX509KeyPair("testdata/certs/foo.crt", "testdata/secrets/foo.key")
assert.FatalError(t, err) require.NoError(t, err)
transport := http.DefaultTransport.(*http.Transport).Clone() transport := http.DefaultTransport.(*http.Transport).Clone()
transport.TLSClientConfig = &tls.Config{ transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: true,
@ -554,12 +606,19 @@ func TestWebhook_Do(t *testing.T) {
Transport: transport, Transport: transport,
} }
reqBody, err := webhook.NewRequestBody(webhook.WithX509CertificateRequest(csr)) reqBody, err := webhook.NewRequestBody(webhook.WithX509CertificateRequest(csr))
assert.FatalError(t, err) require.NoError(t, err)
_, err = wh.Do(client, reqBody, nil)
assert.FatalError(t, err) ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
_, err = wh.DoWithContext(ctx, client, reqBody, nil)
require.NoError(t, err)
ctx, cancel = context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
wh.DisableTLSClientAuth = true wh.DisableTLSClientAuth = true
_, err = wh.Do(client, reqBody, nil) _, err = wh.DoWithContext(ctx, client, reqBody, nil)
assert.Error(t, err) require.Error(t, err)
}) })
} }

@ -813,7 +813,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
} }
tot++ tot++
} }
if len(tc.claims.Step.SSH.CertType) > 0 { if tc.claims.Step.SSH.CertType != "" {
assert.Equals(t, tot, 12) assert.Equals(t, tot, 12)
} else { } else {
assert.Equals(t, tot, 10) assert.Equals(t, tot, 10)

@ -201,6 +201,7 @@ func (a *Authority) generateProvisionerConfig(ctx context.Context) (provisioner.
AuthorizeRenewFunc: a.authorizeRenewFunc, AuthorizeRenewFunc: a.authorizeRenewFunc,
AuthorizeSSHRenewFunc: a.authorizeSSHRenewFunc, AuthorizeSSHRenewFunc: a.authorizeSSHRenewFunc,
WebhookClient: a.webhookClient, WebhookClient: a.webhookClient,
SCEPKeyManager: a.scepKeyManager,
}, nil }, nil
} }
@ -425,25 +426,25 @@ func ValidateClaims(c *linkedca.Claims) error {
// ValidateDurations validates the Durations type. // ValidateDurations validates the Durations type.
func ValidateDurations(d *linkedca.Durations) error { func ValidateDurations(d *linkedca.Durations) error {
var ( var (
err error err error
min, max, def *provisioner.Duration minDur, maxDur, def *provisioner.Duration
) )
if d.Min != "" { if d.Min != "" {
min, err = provisioner.NewDuration(d.Min) minDur, err = provisioner.NewDuration(d.Min)
if err != nil { if err != nil {
return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' is invalid", d.Min) return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' is invalid", d.Min)
} }
if min.Value() < 0 { if minDur.Value() < 0 {
return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' cannot be less than 0", d.Min) return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' cannot be less than 0", d.Min)
} }
} }
if d.Max != "" { if d.Max != "" {
max, err = provisioner.NewDuration(d.Max) maxDur, err = provisioner.NewDuration(d.Max)
if err != nil { if err != nil {
return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' is invalid", d.Max) return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' is invalid", d.Max)
} }
if max.Value() < 0 { if maxDur.Value() < 0 {
return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' cannot be less than 0", d.Max) return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' cannot be less than 0", d.Max)
} }
} }
@ -456,15 +457,15 @@ func ValidateDurations(d *linkedca.Durations) error {
return admin.WrapError(admin.ErrorBadRequestType, err, "default duration '%s' cannot be less than 0", d.Default) return admin.WrapError(admin.ErrorBadRequestType, err, "default duration '%s' cannot be less than 0", d.Default)
} }
} }
if d.Min != "" && d.Max != "" && min.Value() > max.Value() { if d.Min != "" && d.Max != "" && minDur.Value() > maxDur.Value() {
return admin.NewError(admin.ErrorBadRequestType, return admin.NewError(admin.ErrorBadRequestType,
"min duration '%s' cannot be greater than max duration '%s'", d.Min, d.Max) "min duration '%s' cannot be greater than max duration '%s'", d.Min, d.Max)
} }
if d.Min != "" && d.Default != "" && min.Value() > def.Value() { if d.Min != "" && d.Default != "" && minDur.Value() > def.Value() {
return admin.NewError(admin.ErrorBadRequestType, return admin.NewError(admin.ErrorBadRequestType,
"min duration '%s' cannot be greater than default duration '%s'", d.Min, d.Default) "min duration '%s' cannot be greater than default duration '%s'", d.Min, d.Default)
} }
if d.Default != "" && d.Max != "" && min.Value() > def.Value() { if d.Default != "" && d.Max != "" && minDur.Value() > def.Value() {
return admin.NewError(admin.ErrorBadRequestType, return admin.NewError(admin.ErrorBadRequestType,
"default duration '%s' cannot be greater than max duration '%s'", d.Default, d.Max) "default duration '%s' cannot be greater than max duration '%s'", d.Default, d.Max)
} }
@ -607,20 +608,20 @@ func provisionerWebhookToLinkedca(pwh *provisioner.Webhook) *linkedca.Webhook {
return lwh return lwh
} }
func durationsToCertificates(d *linkedca.Durations) (min, max, def *provisioner.Duration, err error) { func durationsToCertificates(d *linkedca.Durations) (minDur, maxDur, def *provisioner.Duration, err error) {
if len(d.Min) > 0 { if d.Min != "" {
min, err = provisioner.NewDuration(d.Min) minDur, err = provisioner.NewDuration(d.Min)
if err != nil { if err != nil {
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing minimum duration '%s'", d.Min) return nil, nil, nil, admin.WrapErrorISE(err, "error parsing minimum duration '%s'", d.Min)
} }
} }
if len(d.Max) > 0 { if d.Max != "" {
max, err = provisioner.NewDuration(d.Max) maxDur, err = provisioner.NewDuration(d.Max)
if err != nil { if err != nil {
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing maximum duration '%s'", d.Max) return nil, nil, nil, admin.WrapErrorISE(err, "error parsing maximum duration '%s'", d.Max)
} }
} }
if len(d.Default) > 0 { if d.Default != "" {
def, err = provisioner.NewDuration(d.Default) def, err = provisioner.NewDuration(d.Default)
if err != nil { if err != nil {
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing default duration '%s'", d.Default) return nil, nil, nil, admin.WrapErrorISE(err, "error parsing default duration '%s'", d.Default)
@ -917,6 +918,8 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface,
Domains: cfg.Domains, Domains: cfg.Domains,
Groups: cfg.Groups, Groups: cfg.Groups,
ListenAddress: cfg.ListenAddress, ListenAddress: cfg.ListenAddress,
Scopes: cfg.Scopes,
AuthParams: cfg.AuthParams,
Claims: claims, Claims: claims,
Options: options, Options: options,
}, nil }, nil
@ -1065,6 +1068,8 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro
Groups: p.Groups, Groups: p.Groups,
ListenAddress: p.ListenAddress, ListenAddress: p.ListenAddress,
TenantId: p.TenantID, TenantId: p.TenantID,
Scopes: p.Scopes,
AuthParams: p.AuthParams,
}, },
}, },
}, },

@ -149,7 +149,7 @@ func TestAuthority_LoadProvisionerByCertificate(t *testing.T) {
opts, err := a.Authorize(ctx, token) opts, err := a.Authorize(ctx, token)
require.NoError(t, err) require.NoError(t, err)
opts = append(opts, extraOpts...) opts = append(opts, extraOpts...)
certs, err := a.Sign(csr, provisioner.SignOptions{}, opts...) certs, err := a.SignWithContext(ctx, csr, provisioner.SignOptions{}, opts...)
require.NoError(t, err) require.NoError(t, err)
return certs[0] return certs[0]
} }

@ -45,7 +45,7 @@ func (a *Authority) GetRoots() ([]*x509.Certificate, error) {
// GetFederation returns all the root certificates in the federation. // GetFederation returns all the root certificates in the federation.
// This method implements the Authority interface. // This method implements the Authority interface.
func (a *Authority) GetFederation() (federation []*x509.Certificate, err error) { func (a *Authority) GetFederation() (federation []*x509.Certificate, err error) {
a.certificates.Range(func(k, v interface{}) bool { a.certificates.Range(func(_, v interface{}) bool {
crt, ok := v.(*x509.Certificate) crt, ok := v.(*x509.Certificate)
if !ok { if !ok {
federation = nil federation = nil
@ -57,3 +57,26 @@ func (a *Authority) GetFederation() (federation []*x509.Certificate, err error)
}) })
return return
} }
// GetIntermediateCertificate return the intermediate certificate that issues
// the leaf certificates in the CA.
//
// This method can return nil if the CA is configured with a Certificate
// Authority Service (CAS) that does not implement the
// CertificateAuthorityGetter interface.
func (a *Authority) GetIntermediateCertificate() *x509.Certificate {
if len(a.intermediateX509Certs) > 0 {
return a.intermediateX509Certs[0]
}
return nil
}
// GetIntermediateCertificates returns a list of all intermediate certificates
// configured. The first certificate in the list will be the issuer certificate.
//
// This method can return an empty list or nil if the CA is configured with a
// Certificate Authority Service (CAS) that does not implement the
// CertificateAuthorityGetter interface.
func (a *Authority) GetIntermediateCertificates() []*x509.Certificate {
return a.intermediateX509Certs
}

@ -2,15 +2,18 @@ package authority
import ( import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"errors" "errors"
"net/http" "net/http"
"reflect" "reflect"
"testing" "testing"
"go.step.sm/crypto/pemutil"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/api/render"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/pemutil"
) )
func TestRoot(t *testing.T) { func TestRoot(t *testing.T) {
@ -152,3 +155,63 @@ func TestAuthority_GetFederation(t *testing.T) {
}) })
} }
} }
func TestAuthority_GetIntermediateCertificate(t *testing.T) {
ca, err := minica.New(minica.WithRootTemplate(`{
"subject": {{ toJson .Subject }},
"issuer": {{ toJson .Subject }},
"keyUsage": ["certSign", "crlSign"],
"basicConstraints": {
"isCA": true,
"maxPathLen": -1
}
}`), minica.WithIntermediateTemplate(`{
"subject": {{ toJson .Subject }},
"keyUsage": ["certSign", "crlSign"],
"basicConstraints": {
"isCA": true,
"maxPathLen": 1
}
}`))
require.NoError(t, err)
signer, err := keyutil.GenerateDefaultSigner()
require.NoError(t, err)
cert, err := ca.Sign(&x509.Certificate{
Subject: pkix.Name{CommonName: "MiniCA Intermediate CA 0"},
PublicKey: signer.Public(),
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 0,
})
require.NoError(t, err)
type fields struct {
intermediateX509Certs []*x509.Certificate
}
tests := []struct {
name string
fields fields
want *x509.Certificate
wantSlice []*x509.Certificate
}{
{"ok one", fields{[]*x509.Certificate{ca.Intermediate}}, ca.Intermediate, []*x509.Certificate{ca.Intermediate}},
{"ok multiple", fields{[]*x509.Certificate{cert, ca.Intermediate}}, cert, []*x509.Certificate{cert, ca.Intermediate}},
{"ok empty", fields{[]*x509.Certificate{}}, nil, []*x509.Certificate{}},
{"ok nil", fields{nil}, nil, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Authority{
intermediateX509Certs: tt.fields.intermediateX509Certs,
}
if got := a.GetIntermediateCertificate(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Authority.GetIntermediateCertificate() = %v, want %v", got, tt.want)
}
if got := a.GetIntermediateCertificates(); !reflect.DeepEqual(got, tt.wantSlice) {
t.Errorf("Authority.GetIntermediateCertificates() = %v, want %v", got, tt.wantSlice)
}
})
}
}

@ -146,7 +146,13 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user, hostname string) (*
} }
// SignSSH creates a signed SSH certificate with the given public key and options. // SignSSH creates a signed SSH certificate with the given public key and options.
func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
cert, prov, err := a.signSSH(ctx, key, opts, signOpts...)
a.meter.SSHSigned(prov, err)
return cert, err
}
func (a *Authority) signSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, provisioner.Interface, error) {
var ( var (
certOptions []sshutil.Option certOptions []sshutil.Option
mods []provisioner.SSHCertModifier mods []provisioner.SSHCertModifier
@ -155,7 +161,7 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// Validate given options. // Validate given options.
if err := opts.Validate(); err != nil { if err := opts.Validate(); err != nil {
return nil, err return nil, nil, err
} }
// Set backdate with the configured value // Set backdate with the configured value
@ -184,7 +190,7 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// validate the given SSHOptions // validate the given SSHOptions
case provisioner.SSHCertOptionsValidator: case provisioner.SSHCertOptionsValidator:
if err := o.Valid(opts); err != nil { if err := o.Valid(opts); err != nil {
return nil, errs.BadRequestErr(err, "error validating ssh certificate options") return nil, prov, errs.BadRequestErr(err, "error validating ssh certificate options")
} }
// call webhooks // call webhooks
@ -192,7 +198,7 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
webhookCtl = o webhookCtl = o
default: default:
return nil, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o) return nil, prov, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o)
} }
} }
@ -205,8 +211,8 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
} }
// Call enriching webhooks // Call enriching webhooks
if err := callEnrichingWebhooksSSH(webhookCtl, cr); err != nil { if err := a.callEnrichingWebhooksSSH(ctx, prov, webhookCtl, cr); err != nil {
return nil, errs.ApplyOptions( return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, err.Error()), errs.ForbiddenErr(err, err.Error()),
errs.WithKeyVal("signOptions", signOpts), errs.WithKeyVal("signOptions", signOpts),
) )
@ -216,20 +222,21 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
certificate, err := sshutil.NewCertificate(cr, certOptions...) certificate, err := sshutil.NewCertificate(cr, certOptions...)
if err != nil { if err != nil {
var te *sshutil.TemplateError var te *sshutil.TemplateError
if errors.As(err, &te) { switch {
return nil, errs.ApplyOptions( case errors.As(err, &te):
return nil, prov, errs.ApplyOptions(
errs.BadRequestErr(err, err.Error()), errs.BadRequestErr(err, err.Error()),
errs.WithKeyVal("signOptions", signOpts), errs.WithKeyVal("signOptions", signOpts),
) )
} case strings.HasPrefix(err.Error(), "error unmarshaling certificate"):
// explicitly check for unmarshaling errors, which are most probably caused by JSON template syntax errors // explicitly check for unmarshaling errors, which are most probably caused by JSON template syntax errors
if strings.HasPrefix(err.Error(), "error unmarshaling certificate") { return nil, prov, errs.InternalServerErr(templatingError(err),
return nil, errs.InternalServerErr(templatingError(err),
errs.WithKeyVal("signOptions", signOpts), errs.WithKeyVal("signOptions", signOpts),
errs.WithMessage("error applying certificate template"), errs.WithMessage("error applying certificate template"),
) )
default:
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH")
} }
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH")
} }
// Get actual *ssh.Certificate and continue with provisioner modifiers. // Get actual *ssh.Certificate and continue with provisioner modifiers.
@ -238,13 +245,13 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// Use SignSSHOptions to modify the certificate validity. It will be later // Use SignSSHOptions to modify the certificate validity. It will be later
// checked or set if not defined. // checked or set if not defined.
if err := opts.ModifyValidity(certTpl); err != nil { if err := opts.ModifyValidity(certTpl); err != nil {
return nil, errs.BadRequestErr(err, err.Error()) return nil, prov, errs.BadRequestErr(err, err.Error())
} }
// Use provisioner modifiers. // Use provisioner modifiers.
for _, m := range mods { for _, m := range mods {
if err := m.Modify(certTpl, opts); err != nil { if err := m.Modify(certTpl, opts); err != nil {
return nil, errs.ForbiddenErr(err, "error creating ssh certificate") return nil, prov, errs.ForbiddenErr(err, "error creating ssh certificate")
} }
} }
@ -253,32 +260,32 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
switch certTpl.CertType { switch certTpl.CertType {
case ssh.UserCert: case ssh.UserCert:
if a.sshCAUserCertSignKey == nil { if a.sshCAUserCertSignKey == nil {
return nil, errs.NotImplemented("authority.SignSSH: user certificate signing is not enabled") return nil, prov, errs.NotImplemented("authority.SignSSH: user certificate signing is not enabled")
} }
signer = a.sshCAUserCertSignKey signer = a.sshCAUserCertSignKey
case ssh.HostCert: case ssh.HostCert:
if a.sshCAHostCertSignKey == nil { if a.sshCAHostCertSignKey == nil {
return nil, errs.NotImplemented("authority.SignSSH: host certificate signing is not enabled") return nil, prov, errs.NotImplemented("authority.SignSSH: host certificate signing is not enabled")
} }
signer = a.sshCAHostCertSignKey signer = a.sshCAHostCertSignKey
default: default:
return nil, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType) return nil, prov, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType)
} }
// Check if authority is allowed to sign the certificate // Check if authority is allowed to sign the certificate
if err := a.isAllowedToSignSSHCertificate(certTpl); err != nil { if err := a.isAllowedToSignSSHCertificate(certTpl); err != nil {
var ee *errs.Error var ee *errs.Error
if errors.As(err, &ee) { if errors.As(err, &ee) {
return nil, ee return nil, prov, ee
} }
return nil, errs.InternalServerErr(err, return nil, prov, errs.InternalServerErr(err,
errs.WithMessage("authority.SignSSH: error creating ssh certificate"), errs.WithMessage("authority.SignSSH: error creating ssh certificate"),
) )
} }
// Send certificate to webhooks for authorization // Send certificate to webhooks for authorization
if err := callAuthorizingWebhooksSSH(webhookCtl, certificate, certTpl); err != nil { if err := a.callAuthorizingWebhooksSSH(ctx, prov, webhookCtl, certificate, certTpl); err != nil {
return nil, errs.ApplyOptions( return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "authority.SignSSH: error signing certificate"), errs.ForbiddenErr(err, "authority.SignSSH: error signing certificate"),
) )
} }
@ -286,21 +293,21 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision
// Sign certificate. // Sign certificate.
cert, err := sshutil.CreateCertificate(certTpl, signer) cert, err := sshutil.CreateCertificate(certTpl, signer)
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate") return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate")
} }
// User provisioners validators. // User provisioners validators.
for _, v := range validators { for _, v := range validators {
if err := v.Valid(cert, opts); err != nil { if err := v.Valid(cert, opts); err != nil {
return nil, errs.ForbiddenErr(err, "error validating ssh certificate") return nil, prov, errs.ForbiddenErr(err, "error validating ssh certificate")
} }
} }
if err = a.storeSSHCertificate(prov, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { if err := a.storeSSHCertificate(prov, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db") return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db")
} }
return cert, nil return cert, prov, nil
} }
// isAllowedToSignSSHCertificate checks if the Authority is allowed to sign the SSH certificate. // isAllowedToSignSSHCertificate checks if the Authority is allowed to sign the SSH certificate.
@ -310,12 +317,18 @@ func (a *Authority) isAllowedToSignSSHCertificate(cert *ssh.Certificate) error {
// RenewSSH creates a signed SSH certificate using the old SSH certificate as a template. // RenewSSH creates a signed SSH certificate using the old SSH certificate as a template.
func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, error) { func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, error) {
cert, prov, err := a.renewSSH(ctx, oldCert)
a.meter.SSHRenewed(prov, err)
return cert, err
}
func (a *Authority) renewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, provisioner.Interface, error) {
if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 { if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
return nil, errs.BadRequest("cannot renew a certificate without validity period") return nil, nil, errs.BadRequest("cannot renew a certificate without validity period")
} }
if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil { if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil {
return nil, err return nil, nil, err
} }
// Attempt to extract the provisioner from the token. // Attempt to extract the provisioner from the token.
@ -348,36 +361,41 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss
switch certTpl.CertType { switch certTpl.CertType {
case ssh.UserCert: case ssh.UserCert:
if a.sshCAUserCertSignKey == nil { if a.sshCAUserCertSignKey == nil {
return nil, errs.NotImplemented("renewSSH: user certificate signing is not enabled") return nil, prov, errs.NotImplemented("renewSSH: user certificate signing is not enabled")
} }
signer = a.sshCAUserCertSignKey signer = a.sshCAUserCertSignKey
case ssh.HostCert: case ssh.HostCert:
if a.sshCAHostCertSignKey == nil { if a.sshCAHostCertSignKey == nil {
return nil, errs.NotImplemented("renewSSH: host certificate signing is not enabled") return nil, prov, errs.NotImplemented("renewSSH: host certificate signing is not enabled")
} }
signer = a.sshCAHostCertSignKey signer = a.sshCAHostCertSignKey
default: default:
return nil, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", certTpl.CertType) return nil, prov, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", certTpl.CertType)
} }
// Sign certificate. // Sign certificate.
cert, err := sshutil.CreateCertificate(certTpl, signer) cert, err := sshutil.CreateCertificate(certTpl, signer)
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
} }
if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { if err := a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db") return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db")
} }
return cert, nil return cert, prov, nil
} }
// RekeySSH creates a signed SSH certificate using the old SSH certificate as a template. // RekeySSH creates a signed SSH certificate using the old SSH certificate as a template.
func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
var validators []provisioner.SSHCertValidator cert, prov, err := a.rekeySSH(ctx, oldCert, pub, signOpts...)
a.meter.SSHRekeyed(prov, err)
return cert, err
}
func (a *Authority) rekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, provisioner.Interface, error) {
var prov provisioner.Interface var prov provisioner.Interface
var validators []provisioner.SSHCertValidator
for _, op := range signOpts { for _, op := range signOpts {
switch o := op.(type) { switch o := op.(type) {
// Capture current provisioner // Capture current provisioner
@ -387,16 +405,16 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
case provisioner.SSHCertValidator: case provisioner.SSHCertValidator:
validators = append(validators, o) validators = append(validators, o)
default: default:
return nil, errs.InternalServer("rekeySSH; invalid extra option type %T", o) return nil, prov, errs.InternalServer("rekeySSH; invalid extra option type %T", o)
} }
} }
if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 { if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
return nil, errs.BadRequest("cannot rekey a certificate without validity period") return nil, prov, errs.BadRequest("cannot rekey a certificate without validity period")
} }
if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil { if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil {
return nil, err return nil, prov, err
} }
backdate := a.config.AuthorityConfig.Backdate.Duration backdate := a.config.AuthorityConfig.Backdate.Duration
@ -423,37 +441,37 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
switch cert.CertType { switch cert.CertType {
case ssh.UserCert: case ssh.UserCert:
if a.sshCAUserCertSignKey == nil { if a.sshCAUserCertSignKey == nil {
return nil, errs.NotImplemented("rekeySSH; user certificate signing is not enabled") return nil, prov, errs.NotImplemented("rekeySSH; user certificate signing is not enabled")
} }
signer = a.sshCAUserCertSignKey signer = a.sshCAUserCertSignKey
case ssh.HostCert: case ssh.HostCert:
if a.sshCAHostCertSignKey == nil { if a.sshCAHostCertSignKey == nil {
return nil, errs.NotImplemented("rekeySSH; host certificate signing is not enabled") return nil, prov, errs.NotImplemented("rekeySSH; host certificate signing is not enabled")
} }
signer = a.sshCAHostCertSignKey signer = a.sshCAHostCertSignKey
default: default:
return nil, errs.BadRequest("unexpected certificate type '%d'", cert.CertType) return nil, prov, errs.BadRequest("unexpected certificate type '%d'", cert.CertType)
} }
var err error var err error
// Sign certificate. // Sign certificate.
cert, err = sshutil.CreateCertificate(cert, signer) cert, err = sshutil.CreateCertificate(cert, signer)
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
} }
// Apply validators from provisioner. // Apply validators from provisioner.
for _, v := range validators { for _, v := range validators {
if err := v.Valid(cert, provisioner.SignSSHOptions{Backdate: backdate}); err != nil { if err := v.Valid(cert, provisioner.SignSSHOptions{Backdate: backdate}); err != nil {
return nil, errs.ForbiddenErr(err, "error validating ssh certificate") return nil, prov, errs.ForbiddenErr(err, "error validating ssh certificate")
} }
} }
if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { if err := a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db") return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db")
} }
return cert, nil return cert, prov, nil
} }
func (a *Authority) storeSSHCertificate(prov provisioner.Interface, cert *ssh.Certificate) error { func (a *Authority) storeSSHCertificate(prov provisioner.Interface, cert *ssh.Certificate) error {
@ -653,28 +671,34 @@ func (a *Authority) getAddUserCommand(principal string) string {
return strings.ReplaceAll(cmd, "<principal>", principal) return strings.ReplaceAll(cmd, "<principal>", principal)
} }
func callEnrichingWebhooksSSH(webhookCtl webhookController, cr sshutil.CertificateRequest) error { func (a *Authority) callEnrichingWebhooksSSH(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, cr sshutil.CertificateRequest) (err error) {
if webhookCtl == nil { if webhookCtl == nil {
return nil return
} }
whEnrichReq, err := webhook.NewRequestBody( defer func() { a.meter.SSHWebhookEnriched(prov, err) }()
var whEnrichReq *webhook.RequestBody
if whEnrichReq, err = webhook.NewRequestBody(
webhook.WithSSHCertificateRequest(cr), webhook.WithSSHCertificateRequest(cr),
) ); err == nil {
if err != nil { err = webhookCtl.Enrich(ctx, whEnrichReq)
return err
} }
return webhookCtl.Enrich(whEnrichReq)
return
} }
func callAuthorizingWebhooksSSH(webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) error { func (a *Authority) callAuthorizingWebhooksSSH(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) (err error) {
if webhookCtl == nil { if webhookCtl == nil {
return nil return
} }
whAuthBody, err := webhook.NewRequestBody( defer func() { a.meter.SSHWebhookAuthorized(prov, err) }()
var whAuthBody *webhook.RequestBody
if whAuthBody, err = webhook.NewRequestBody(
webhook.WithSSHCertificate(cert, certTpl), webhook.WithSSHCertificate(cert, certTpl),
) ); err == nil {
if err != nil { err = webhookCtl.Authorize(ctx, whAuthBody)
return err
} }
return webhookCtl.Authorize(whAuthBody)
return
} }

@ -59,7 +59,7 @@ var (
) )
func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc { func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc {
return func(crt *x509.Certificate, opts provisioner.SignOptions) error { return func(crt *x509.Certificate, _ provisioner.SignOptions) error {
if def == nil { if def == nil {
return errors.New("default ASN1DN template cannot be nil") return errors.New("default ASN1DN template cannot be nil")
} }
@ -91,8 +91,38 @@ func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc {
} }
} }
// Sign creates a signed certificate from a certificate signing request. // GetX509Signer returns a [crypto.Signer] implementation using the intermediate
// key.
//
// This method can return a [NotImplementedError] if the CA is configured with a
// Certificate Authority Service (CAS) that does not implement the
// CertificateAuthoritySigner interface.
//
// [NotImplementedError]: https://pkg.go.dev/github.com/smallstep/certificates/cas/apiv1#NotImplementedError
func (a *Authority) GetX509Signer() (crypto.Signer, error) {
if s, ok := a.x509CAService.(casapi.CertificateAuthoritySigner); ok {
return s.GetSigner()
}
return nil, casapi.NotImplementedError{}
}
// Sign creates a signed certificate from a certificate signing request. It
// creates a new context.Context, and calls into SignWithContext.
//
// Deprecated: Use authority.SignWithContext with an actual context.Context.
func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
return a.SignWithContext(context.Background(), csr, signOpts, extraOpts...)
}
// SignWithContext creates a signed certificate from a certificate signing
// request, taking the provided context.Context.
func (a *Authority) SignWithContext(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
chain, prov, err := a.signX509(ctx, csr, signOpts, extraOpts...)
a.meter.X509Signed(prov, err)
return chain, err
}
func (a *Authority) signX509(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, provisioner.Interface, error) {
var ( var (
certOptions []x509util.Option certOptions []x509util.Option
certValidators []provisioner.CertificateValidator certValidators []provisioner.CertificateValidator
@ -100,9 +130,9 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
certEnforcers []provisioner.CertificateEnforcer certEnforcers []provisioner.CertificateEnforcer
) )
opts := []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} opts := []any{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)}
if err := csr.CheckSignature(); err != nil { if err := csr.CheckSignature(); err != nil {
return nil, errs.ApplyOptions( return nil, nil, errs.ApplyOptions(
errs.BadRequestErr(err, "invalid certificate request"), errs.BadRequestErr(err, "invalid certificate request"),
opts..., opts...,
) )
@ -111,10 +141,12 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Set backdate with the configured value // Set backdate with the configured value
signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration
var prov provisioner.Interface var (
var pInfo *casapi.ProvisionerInfo prov provisioner.Interface
var attData *provisioner.AttestationData pInfo *casapi.ProvisionerInfo
var webhookCtl webhookController attData *provisioner.AttestationData
webhookCtl webhookController
)
for _, op := range extraOpts { for _, op := range extraOpts {
switch k := op.(type) { switch k := op.(type) {
// Capture current provisioner // Capture current provisioner
@ -132,7 +164,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Validate the given certificate request. // Validate the given certificate request.
case provisioner.CertificateRequestValidator: case provisioner.CertificateRequestValidator:
if err := k.Valid(csr); err != nil { if err := k.Valid(csr); err != nil {
return nil, errs.ApplyOptions( return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error validating certificate request"), errs.ForbiddenErr(err, "error validating certificate request"),
opts..., opts...,
) )
@ -159,45 +191,46 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
webhookCtl = k webhookCtl = k
default: default:
return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...) return nil, prov, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]any{k}, opts...)...)
} }
} }
if err := callEnrichingWebhooksX509(webhookCtl, attData, csr); err != nil { if err := a.callEnrichingWebhooksX509(ctx, prov, webhookCtl, attData, csr); err != nil {
return nil, errs.ApplyOptions( return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, err.Error()), errs.ForbiddenErr(err, err.Error()),
errs.WithKeyVal("csr", csr), errs.WithKeyVal("csr", csr),
errs.WithKeyVal("signOptions", signOpts), errs.WithKeyVal("signOptions", signOpts),
) )
} }
cert, err := x509util.NewCertificate(csr, certOptions...) crt, err := x509util.NewCertificate(csr, certOptions...)
if err != nil { if err != nil {
var te *x509util.TemplateError var te *x509util.TemplateError
if errors.As(err, &te) { switch {
return nil, errs.ApplyOptions( case errors.As(err, &te):
return nil, prov, errs.ApplyOptions(
errs.BadRequestErr(err, err.Error()), errs.BadRequestErr(err, err.Error()),
errs.WithKeyVal("csr", csr), errs.WithKeyVal("csr", csr),
errs.WithKeyVal("signOptions", signOpts), errs.WithKeyVal("signOptions", signOpts),
) )
} case strings.HasPrefix(err.Error(), "error unmarshaling certificate"):
// explicitly check for unmarshaling errors, which are most probably caused by JSON template (syntax) errors // explicitly check for unmarshaling errors, which are most probably caused by JSON template (syntax) errors
if strings.HasPrefix(err.Error(), "error unmarshaling certificate") { return nil, prov, errs.InternalServerErr(templatingError(err),
return nil, errs.InternalServerErr(templatingError(err),
errs.WithKeyVal("csr", csr), errs.WithKeyVal("csr", csr),
errs.WithKeyVal("signOptions", signOpts), errs.WithKeyVal("signOptions", signOpts),
errs.WithMessage("error applying certificate template"), errs.WithMessage("error applying certificate template"),
) )
default:
return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...)
} }
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...)
} }
// Certificate modifiers before validation // Certificate modifiers before validation
leaf := cert.GetCertificate() leaf := crt.GetCertificate()
// Set default subject // Set default subject
if err := withDefaultASN1DN(a.config.AuthorityConfig.Template).Modify(leaf, signOpts); err != nil { if err := withDefaultASN1DN(a.config.AuthorityConfig.Template).Modify(leaf, signOpts); err != nil {
return nil, errs.ApplyOptions( return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"), errs.ForbiddenErr(err, "error creating certificate"),
opts..., opts...,
) )
@ -205,7 +238,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
for _, m := range certModifiers { for _, m := range certModifiers {
if err := m.Modify(leaf, signOpts); err != nil { if err := m.Modify(leaf, signOpts); err != nil {
return nil, errs.ApplyOptions( return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"), errs.ForbiddenErr(err, "error creating certificate"),
opts..., opts...,
) )
@ -215,7 +248,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Certificate validation. // Certificate validation.
for _, v := range certValidators { for _, v := range certValidators {
if err := v.Valid(leaf, signOpts); err != nil { if err := v.Valid(leaf, signOpts); err != nil {
return nil, errs.ApplyOptions( return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error validating certificate"), errs.ForbiddenErr(err, "error validating certificate"),
opts..., opts...,
) )
@ -224,8 +257,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Certificate modifiers after validation // Certificate modifiers after validation
for _, m := range certEnforcers { for _, m := range certEnforcers {
if err := m.Enforce(leaf); err != nil { if err = m.Enforce(leaf); err != nil {
return nil, errs.ApplyOptions( return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"), errs.ForbiddenErr(err, "error creating certificate"),
opts..., opts...,
) )
@ -234,8 +267,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Process injected modifiers after validation // Process injected modifiers after validation
for _, m := range a.x509Enforcers { for _, m := range a.x509Enforcers {
if err := m.Enforce(leaf); err != nil { if err = m.Enforce(leaf); err != nil {
return nil, errs.ApplyOptions( return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"), errs.ForbiddenErr(err, "error creating certificate"),
opts..., opts...,
) )
@ -243,12 +276,12 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
} }
// Check if authority is allowed to sign the certificate // Check if authority is allowed to sign the certificate
if err := a.isAllowedToSignX509Certificate(leaf); err != nil { if err = a.isAllowedToSignX509Certificate(leaf); err != nil {
var ee *errs.Error var ee *errs.Error
if errors.As(err, &ee) { if errors.As(err, &ee) {
return nil, errs.ApplyOptions(ee, opts...) return nil, prov, errs.ApplyOptions(ee, opts...)
} }
return nil, errs.InternalServerErr(err, return nil, prov, errs.InternalServerErr(err,
errs.WithKeyVal("csr", csr), errs.WithKeyVal("csr", csr),
errs.WithKeyVal("signOptions", signOpts), errs.WithKeyVal("signOptions", signOpts),
errs.WithMessage("error creating certificate"), errs.WithMessage("error creating certificate"),
@ -256,8 +289,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
} }
// Send certificate to webhooks for authorization // Send certificate to webhooks for authorization
if err := callAuthorizingWebhooksX509(webhookCtl, cert, leaf, attData); err != nil { if err := a.callAuthorizingWebhooksX509(ctx, prov, webhookCtl, crt, leaf, attData); err != nil {
return nil, errs.ApplyOptions( return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"), errs.ForbiddenErr(err, "error creating certificate"),
opts..., opts...,
) )
@ -265,6 +298,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
// Sign certificate // Sign certificate
lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate)) lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate))
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
Template: leaf, Template: leaf,
CSR: csr, CSR: csr,
@ -273,23 +307,22 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
Provisioner: pInfo, Provisioner: pInfo,
}) })
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...) return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...)
} }
fullchain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) chain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...)
// Wrap provisioner with extra information. // Wrap provisioner with extra information, if not nil
prov = wrapProvisioner(prov, attData) if prov != nil {
prov = wrapProvisioner(prov, attData)
}
// Store certificate in the db. // Store certificate in the db.
if err = a.storeCertificate(prov, fullchain); err != nil { if err := a.storeCertificate(prov, chain); err != nil && !errors.Is(err, db.ErrNotImplemented) {
if !errors.Is(err, db.ErrNotImplemented) { return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error storing certificate in db", opts...)
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Sign; error storing certificate in db", opts...)
}
} }
return fullchain, nil return chain, prov, nil
} }
// isAllowedToSignX509Certificate checks if the Authority is allowed // isAllowedToSignX509Certificate checks if the Authority is allowed
@ -337,14 +370,25 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
// of rekey), and 'NotBefore/NotAfter' (the validity duration of the new // of rekey), and 'NotBefore/NotAfter' (the validity duration of the new
// certificate should be equal to the old one, but starting 'now'). // certificate should be equal to the old one, but starting 'now').
func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) {
chain, prov, err := a.renewContext(ctx, oldCert, pk)
if pk == nil {
a.meter.X509Renewed(prov, err)
} else {
a.meter.X509Rekeyed(prov, err)
}
return chain, err
}
func (a *Authority) renewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, provisioner.Interface, error) {
isRekey := (pk != nil) isRekey := (pk != nil)
opts := []errs.Option{ opts := []errs.Option{
errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()), errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()),
} }
// Check step provisioner extensions // Check step provisioner extensions
if err := a.authorizeRenew(ctx, oldCert); err != nil { prov, err := a.authorizeRenew(ctx, oldCert)
return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) if err != nil {
return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...)
} }
// Durations // Durations
@ -414,15 +458,17 @@ func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate,
// //
// TODO(hslatman,maraino): consider adding policies too and consider if // TODO(hslatman,maraino): consider adding policies too and consider if
// RenewSSH should check policies. // RenewSSH should check policies.
if err := a.constraintsEngine.ValidateCertificate(newCert); err != nil { if err = a.constraintsEngine.ValidateCertificate(newCert); err != nil {
var ee *errs.Error var ee *errs.Error
if errors.As(err, &ee) { switch {
return nil, errs.StatusCodeError(ee.StatusCode(), err, opts...) case errors.As(err, &ee):
return nil, prov, errs.StatusCodeError(ee.StatusCode(), err, opts...)
default:
return nil, prov, errs.InternalServerErr(err,
errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()),
errs.WithMessage("error renewing certificate"),
)
} }
return nil, errs.InternalServerErr(err,
errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()),
errs.WithMessage("error renewing certificate"),
)
} }
// The token can optionally be in the context. If the CA is running in RA // The token can optionally be in the context. If the CA is running in RA
@ -436,17 +482,16 @@ func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate,
Token: token, Token: token,
}) })
if err != nil { if err != nil {
return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...)
} }
fullchain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) chain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...)
if err = a.storeRenewedCertificate(oldCert, fullchain); err != nil {
if !errors.Is(err, db.ErrNotImplemented) { if err = a.storeRenewedCertificate(oldCert, chain); err != nil && !errors.Is(err, db.ErrNotImplemented) {
return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...)
}
} }
return fullchain, nil return chain, prov, nil
} }
// storeCertificate allows to use an extension of the db.AuthDB interface that // storeCertificate allows to use an extension of the db.AuthDB interface that
@ -675,9 +720,17 @@ func (a *Authority) revokeSSH(crt *ssh.Certificate, rci *db.RevokedCertificateIn
return a.db.RevokeSSH(rci) return a.db.RevokeSSH(rci)
} }
// CertificateRevocationListInfo contains a CRL in DER format and associated metadata.
type CertificateRevocationListInfo struct {
Number int64
ExpiresAt time.Time
Duration time.Duration
Data []byte
}
// GetCertificateRevocationList will return the currently generated CRL from the DB, or a not implemented // GetCertificateRevocationList will return the currently generated CRL from the DB, or a not implemented
// error if the underlying AuthDB does not support CRLs // error if the underlying AuthDB does not support CRLs
func (a *Authority) GetCertificateRevocationList() ([]byte, error) { func (a *Authority) GetCertificateRevocationList() (*CertificateRevocationListInfo, error) {
if !a.config.CRL.IsEnabled() { if !a.config.CRL.IsEnabled() {
return nil, errs.Wrap(http.StatusNotFound, errors.Errorf("Certificate Revocation Lists are not enabled"), "authority.GetCertificateRevocationList") return nil, errs.Wrap(http.StatusNotFound, errors.Errorf("Certificate Revocation Lists are not enabled"), "authority.GetCertificateRevocationList")
} }
@ -692,7 +745,12 @@ func (a *Authority) GetCertificateRevocationList() ([]byte, error) {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetCertificateRevocationList") return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetCertificateRevocationList")
} }
return crlInfo.DER, nil return &CertificateRevocationListInfo{
Number: crlInfo.Number,
ExpiresAt: crlInfo.ExpiresAt,
Duration: crlInfo.Duration,
Data: crlInfo.DER,
}, nil
} }
// GenerateCertificateRevocationList generates a DER representation of a signed CRL and stores it in the // GenerateCertificateRevocationList generates a DER representation of a signed CRL and stores it in the
@ -870,10 +928,19 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
return fatal(err) return fatal(err)
} }
// Set the cert lifetime as follows:
// i) If the CA is not a StepCAS RA use 24h, else
// ii) if the CA is a StepCAS RA, leave the lifetime empty and
// let the provisioner of the CA decide the lifetime of the RA cert.
var lifetime time.Duration
if casapi.TypeOf(a.x509CAService) != casapi.StepCAS {
lifetime = 24 * time.Hour
}
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
Template: certTpl, Template: certTpl,
CSR: cr, CSR: cr,
Lifetime: 24 * time.Hour, Lifetime: lifetime,
Backdate: 1 * time.Minute, Backdate: 1 * time.Minute,
IsCAServerCert: true, IsCAServerCert: true,
}) })
@ -952,42 +1019,50 @@ func templatingError(err error) error {
return errors.Wrap(cause, "error applying certificate template") return errors.Wrap(cause, "error applying certificate template")
} }
func callEnrichingWebhooksX509(webhookCtl webhookController, attData *provisioner.AttestationData, csr *x509.CertificateRequest) error { func (a *Authority) callEnrichingWebhooksX509(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, attData *provisioner.AttestationData, csr *x509.CertificateRequest) (err error) {
if webhookCtl == nil { if webhookCtl == nil {
return nil return
} }
defer func() { a.meter.X509WebhookEnriched(prov, err) }()
var attested *webhook.AttestationData var attested *webhook.AttestationData
if attData != nil { if attData != nil {
attested = &webhook.AttestationData{ attested = &webhook.AttestationData{
PermanentIdentifier: attData.PermanentIdentifier, PermanentIdentifier: attData.PermanentIdentifier,
} }
} }
whEnrichReq, err := webhook.NewRequestBody(
var whEnrichReq *webhook.RequestBody
if whEnrichReq, err = webhook.NewRequestBody(
webhook.WithX509CertificateRequest(csr), webhook.WithX509CertificateRequest(csr),
webhook.WithAttestationData(attested), webhook.WithAttestationData(attested),
) ); err == nil {
if err != nil { err = webhookCtl.Enrich(ctx, whEnrichReq)
return err
} }
return webhookCtl.Enrich(whEnrichReq)
return
} }
func callAuthorizingWebhooksX509(webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) error { func (a *Authority) callAuthorizingWebhooksX509(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) (err error) {
if webhookCtl == nil { if webhookCtl == nil {
return nil return
} }
defer func() { a.meter.X509WebhookAuthorized(prov, err) }()
var attested *webhook.AttestationData var attested *webhook.AttestationData
if attData != nil { if attData != nil {
attested = &webhook.AttestationData{ attested = &webhook.AttestationData{
PermanentIdentifier: attData.PermanentIdentifier, PermanentIdentifier: attData.PermanentIdentifier,
} }
} }
whAuthBody, err := webhook.NewRequestBody(
var whAuthBody *webhook.RequestBody
if whAuthBody, err = webhook.NewRequestBody(
webhook.WithX509Certificate(cert, leaf), webhook.WithX509Certificate(cert, leaf),
webhook.WithAttestationData(attested), webhook.WithAttestationData(attested),
) ); err == nil {
if err != nil { err = webhookCtl.Authorize(ctx, whAuthBody)
return err
} }
return webhookCtl.Authorize(whAuthBody)
return
} }

@ -15,6 +15,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"reflect" "reflect"
"strings"
"testing" "testing"
"time" "time"
@ -24,15 +25,17 @@ import (
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/policy" "github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/cas/softcas" "github.com/smallstep/certificates/cas/softcas"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/nosql/database" "github.com/smallstep/nosql/database"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var ( var (
@ -80,25 +83,25 @@ func generateCertificate(t *testing.T, commonName string, sans []string, opts ..
t.Helper() t.Helper()
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.FatalError(t, err) require.NoError(t, err)
cr, err := x509util.CreateCertificateRequest(commonName, sans, priv) cr, err := x509util.CreateCertificateRequest(commonName, sans, priv)
assert.FatalError(t, err) require.NoError(t, err)
template, err := x509util.NewCertificate(cr) template, err := x509util.NewCertificate(cr)
assert.FatalError(t, err) require.NoError(t, err)
cert := template.GetCertificate() cert := template.GetCertificate()
for _, m := range opts { for _, m := range opts {
switch m := m.(type) { switch m := m.(type) {
case provisioner.CertificateModifierFunc: case provisioner.CertificateModifierFunc:
err = m.Modify(cert, provisioner.SignOptions{}) err = m.Modify(cert, provisioner.SignOptions{})
assert.FatalError(t, err) require.NoError(t, err)
case signerFunc: case signerFunc:
cert, err = m(cert, priv.Public()) cert, err = m(cert, priv.Public())
assert.FatalError(t, err) require.NoError(t, err)
default: default:
t.Fatalf("unknown type %T", m) require.Fail(t, "", "unknown type %T", m)
} }
} }
@ -108,36 +111,36 @@ func generateCertificate(t *testing.T, commonName string, sans []string, opts ..
func generateRootCertificate(t *testing.T) (*x509.Certificate, crypto.Signer) { func generateRootCertificate(t *testing.T) (*x509.Certificate, crypto.Signer) {
t.Helper() t.Helper()
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.FatalError(t, err) require.NoError(t, err)
cr, err := x509util.CreateCertificateRequest("TestRootCA", nil, priv) cr, err := x509util.CreateCertificateRequest("TestRootCA", nil, priv)
assert.FatalError(t, err) require.NoError(t, err)
data := x509util.CreateTemplateData("TestRootCA", nil) data := x509util.CreateTemplateData("TestRootCA", nil)
template, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data)) template, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data))
assert.FatalError(t, err) require.NoError(t, err)
cert := template.GetCertificate() cert := template.GetCertificate()
cert, err = x509util.CreateCertificate(cert, cert, priv.Public(), priv) cert, err = x509util.CreateCertificate(cert, cert, priv.Public(), priv)
assert.FatalError(t, err) require.NoError(t, err)
return cert, priv return cert, priv
} }
func generateIntermidiateCertificate(t *testing.T, issuer *x509.Certificate, signer crypto.Signer) (*x509.Certificate, crypto.Signer) { func generateIntermidiateCertificate(t *testing.T, issuer *x509.Certificate, signer crypto.Signer) (*x509.Certificate, crypto.Signer) {
t.Helper() t.Helper()
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.FatalError(t, err) require.NoError(t, err)
cr, err := x509util.CreateCertificateRequest("TestIntermediateCA", nil, priv) cr, err := x509util.CreateCertificateRequest("TestIntermediateCA", nil, priv)
assert.FatalError(t, err) require.NoError(t, err)
data := x509util.CreateTemplateData("TestIntermediateCA", nil) data := x509util.CreateTemplateData("TestIntermediateCA", nil)
template, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data)) template, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data))
assert.FatalError(t, err) require.NoError(t, err)
cert := template.GetCertificate() cert := template.GetCertificate()
cert, err = x509util.CreateCertificate(cert, issuer, priv.Public(), signer) cert, err = x509util.CreateCertificate(cert, issuer, priv.Public(), signer)
assert.FatalError(t, err) require.NoError(t, err)
return cert, priv return cert, priv
} }
@ -192,9 +195,9 @@ func getCSR(t *testing.T, priv interface{}, opts ...func(*x509.CertificateReques
opt(_csr) opt(_csr)
} }
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, _csr, priv) csrBytes, err := x509.CreateCertificateRequest(rand.Reader, _csr, priv)
assert.FatalError(t, err) require.NoError(t, err)
csr, err := x509.ParseCertificateRequest(csrBytes) csr, err := x509.ParseCertificateRequest(csrBytes)
assert.FatalError(t, err) require.NoError(t, err)
return csr return csr
} }
@ -221,6 +224,15 @@ func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) {
return hash[:], nil return hash[:], nil
} }
func assertHasPrefix(t *testing.T, s, p string) bool {
if strings.HasPrefix(s, p) {
return true
}
t.Helper()
t.Errorf("%q is not a prefix of %q", p, s)
return false
}
type basicConstraints struct { type basicConstraints struct {
IsCA bool `asn1:"optional"` IsCA bool `asn1:"optional"`
MaxPathLen int `asn1:"optional,default:-1"` MaxPathLen int `asn1:"optional,default:-1"`
@ -237,12 +249,12 @@ func (e *testEnforcer) Enforce(cert *x509.Certificate) error {
return nil return nil
} }
func TestAuthority_Sign(t *testing.T) { func TestAuthority_SignWithContext(t *testing.T) {
pub, priv, err := keyutil.GenerateDefaultKeyPair() pub, priv, err := keyutil.GenerateDefaultKeyPair()
assert.FatalError(t, err) require.NoError(t, err)
a := testAuthority(t) a := testAuthority(t)
assert.FatalError(t, err) require.NoError(t, err)
a.config.AuthorityConfig.Template = &ASN1DN{ a.config.AuthorityConfig.Template = &ASN1DN{
Country: "Tazmania", Country: "Tazmania",
Organization: "Acme Co", Organization: "Acme Co",
@ -262,12 +274,12 @@ func TestAuthority_Sign(t *testing.T) {
// Create a token to get test extra opts. // Create a token to get test extra opts.
p := a.config.AuthorityConfig.Provisioners[1].(*provisioner.JWK) p := a.config.AuthorityConfig.Provisioners[1].(*provisioner.JWK)
key, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass"))) key, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err) require.NoError(t, err)
token, err := generateToken("smallstep test", "step-cli", testAudiences.Sign[0], []string{"test.smallstep.com"}, time.Now(), key) token, err := generateToken("smallstep test", "step-cli", testAudiences.Sign[0], []string{"test.smallstep.com"}, time.Now(), key)
assert.FatalError(t, err) require.NoError(t, err)
ctx := provisioner.NewContextWithMethod(context.Background(), provisioner.SignMethod) ctx := provisioner.NewContextWithMethod(context.Background(), provisioner.SignMethod)
extraOpts, err := a.Authorize(ctx, token) extraOpts, err := a.Authorize(ctx, token)
assert.FatalError(t, err) require.NoError(t, err)
type signTest struct { type signTest struct {
auth *Authority auth *Authority
@ -372,9 +384,9 @@ W5kR63lNVHBHgQmv5mA8YFsfrJHstaz5k727v2LMHEYIf5/3i16d5zhuxUoaPTYr
ZYtQ9Ot36qc= ZYtQ9Ot36qc=
-----END CERTIFICATE REQUEST-----` -----END CERTIFICATE REQUEST-----`
block, _ := pem.Decode([]byte(shortRSAKeyPEM)) block, _ := pem.Decode([]byte(shortRSAKeyPEM))
assert.FatalError(t, err) require.NoError(t, err)
csr, err := x509.ParseCertificateRequest(block.Bytes) csr, err := x509.ParseCertificateRequest(block.Bytes)
assert.FatalError(t, err) require.NoError(t, err)
return &signTest{ return &signTest{
auth: a, auth: a,
@ -413,10 +425,10 @@ ZYtQ9Ot36qc=
X509: &provisioner.X509Options{Template: `{{ fail "fail message" }}`}, X509: &provisioner.X509Options{Template: `{{ fail "fail message" }}`},
} }
testExtraOpts, err := testAuthority.Authorize(ctx, token) testExtraOpts, err := testAuthority.Authorize(ctx, token)
assert.FatalError(t, err) require.NoError(t, err)
testAuthority.db = &db.MockAuthDB{ testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil return nil
}, },
} }
@ -442,10 +454,10 @@ ZYtQ9Ot36qc=
}, },
} }
testExtraOpts, err := testAuthority.Authorize(ctx, token) testExtraOpts, err := testAuthority.Authorize(ctx, token)
assert.FatalError(t, err) require.NoError(t, err)
testAuthority.db = &db.MockAuthDB{ testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil return nil
}, },
} }
@ -471,10 +483,10 @@ ZYtQ9Ot36qc=
}, },
} }
testExtraOpts, err := testAuthority.Authorize(ctx, token) testExtraOpts, err := testAuthority.Authorize(ctx, token)
assert.FatalError(t, err) require.NoError(t, err)
testAuthority.db = &db.MockAuthDB{ testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil return nil
}, },
} }
@ -492,7 +504,7 @@ ZYtQ9Ot36qc=
aa := testAuthority(t) aa := testAuthority(t)
aa.db = &db.MockAuthDB{ aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil return nil
}, },
} }
@ -517,7 +529,7 @@ ZYtQ9Ot36qc=
})) }))
aa.db = &db.MockAuthDB{ aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil return nil
}, },
} }
@ -537,7 +549,7 @@ ZYtQ9Ot36qc=
aa.db = &db.MockAuthDB{ aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
fmt.Println(crt.Subject) fmt.Println(crt.Subject)
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil return nil
}, },
} }
@ -549,7 +561,7 @@ ZYtQ9Ot36qc=
}, },
} }
engine, err := policy.New(options) engine, err := policy.New(options)
assert.FatalError(t, err) require.NoError(t, err)
aa.policyEngine = engine aa.policyEngine = engine
return &signTest{ return &signTest{
auth: aa, auth: aa,
@ -598,7 +610,7 @@ ZYtQ9Ot36qc=
_a := testAuthority(t) _a := testAuthority(t)
_a.db = &db.MockAuthDB{ _a.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil return nil
}, },
} }
@ -617,7 +629,7 @@ ZYtQ9Ot36qc=
bcExt.Id = asn1.ObjectIdentifier{2, 5, 29, 19} bcExt.Id = asn1.ObjectIdentifier{2, 5, 29, 19}
bcExt.Critical = false bcExt.Critical = false
bcExt.Value, err = asn1.Marshal(basicConstraints{IsCA: true, MaxPathLen: 4}) bcExt.Value, err = asn1.Marshal(basicConstraints{IsCA: true, MaxPathLen: 4})
assert.FatalError(t, err) require.NoError(t, err)
csr := getCSR(t, priv, setExtraExtsCSR([]pkix.Extension{ csr := getCSR(t, priv, setExtraExtsCSR([]pkix.Extension{
bcExt, bcExt,
@ -632,7 +644,7 @@ ZYtQ9Ot36qc=
_a := testAuthority(t) _a := testAuthority(t)
_a.db = &db.MockAuthDB{ _a.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil return nil
}, },
} }
@ -663,10 +675,10 @@ ZYtQ9Ot36qc=
}`}, }`},
} }
testExtraOpts, err := testAuthority.Authorize(ctx, token) testExtraOpts, err := testAuthority.Authorize(ctx, token)
assert.FatalError(t, err) require.NoError(t, err)
testAuthority.db = &db.MockAuthDB{ testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil return nil
}, },
} }
@ -697,10 +709,10 @@ ZYtQ9Ot36qc=
}`}, }`},
} }
testExtraOpts, err := testAuthority.Authorize(ctx, token) testExtraOpts, err := testAuthority.Authorize(ctx, token)
assert.FatalError(t, err) require.NoError(t, err)
testAuthority.db = &db.MockAuthDB{ testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil return nil
}, },
} }
@ -737,7 +749,7 @@ ZYtQ9Ot36qc=
_a.config.AuthorityConfig.Template = &ASN1DN{} _a.config.AuthorityConfig.Template = &ASN1DN{}
_a.db = &db.MockAuthDB{ _a.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject, pkix.Name{}) assert.Equal(t, pkix.Name{}, crt.Subject)
return nil return nil
}, },
} }
@ -762,8 +774,8 @@ ZYtQ9Ot36qc=
aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template
aa.db = &db.MockAuthDB{ aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equals(t, crt.CRLDistributionPoints, []string{"http://ca.example.org/leaf.crl"}) assert.Equal(t, []string{"http://ca.example.org/leaf.crl"}, crt.CRLDistributionPoints)
return nil return nil
}, },
} }
@ -783,7 +795,7 @@ ZYtQ9Ot36qc=
aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template
aa.db = &db.MockAuthDB{ aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error { MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equals(t, crt.Subject.CommonName, "smallstep test") assert.Equal(t, crt.Subject.CommonName, "smallstep test")
return nil return nil
}, },
} }
@ -796,7 +808,7 @@ ZYtQ9Ot36qc=
}, },
} }
engine, err := policy.New(options) engine, err := policy.New(options)
assert.FatalError(t, err) require.NoError(t, err)
aa.policyEngine = engine aa.policyEngine = engine
return &signTest{ return &signTest{
auth: aa, auth: aa,
@ -816,13 +828,13 @@ ZYtQ9Ot36qc=
MStoreCertificateChain: func(prov provisioner.Interface, certs ...*x509.Certificate) error { MStoreCertificateChain: func(prov provisioner.Interface, certs ...*x509.Certificate) error {
p, ok := prov.(attProvisioner) p, ok := prov.(attProvisioner)
if assert.True(t, ok) { if assert.True(t, ok) {
assert.Equals(t, &provisioner.AttestationData{ assert.Equal(t, &provisioner.AttestationData{
PermanentIdentifier: "1234567890", PermanentIdentifier: "1234567890",
}, p.AttestationData()) }, p.AttestationData())
} }
if assert.Len(t, 2, certs) { if assert.Len(t, certs, 2) {
assert.Equals(t, certs[0].Subject.CommonName, "smallstep test") assert.Equal(t, "smallstep test", certs[0].Subject.CommonName)
assert.Equals(t, certs[1].Subject.CommonName, "smallstep Intermediate CA") assert.Equal(t, "smallstep Intermediate CA", certs[1].Subject.CommonName)
} }
return nil return nil
}, },
@ -846,51 +858,50 @@ ZYtQ9Ot36qc=
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := genTestCase(t) tc := genTestCase(t)
certChain, err := tc.auth.Sign(tc.csr, tc.signOpts, tc.extraOpts...) certChain, err := tc.auth.SignWithContext(context.Background(), tc.csr, tc.signOpts, tc.extraOpts...)
if err != nil { if err != nil {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
assert.Nil(t, certChain) assert.Nil(t, certChain)
var sc render.StatusCodedError var sc render.StatusCodedError
assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code) assert.Equal(t, tc.code, sc.StatusCode())
assert.HasPrefix(t, err.Error(), tc.err.Error()) assertHasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error var ctxErr *errs.Error
assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equals(t, ctxErr.Details["csr"], tc.csr) assert.Equal(t, tc.csr, ctxErr.Details["csr"])
assert.Equals(t, ctxErr.Details["signOptions"], tc.signOpts) assert.Equal(t, tc.signOpts, ctxErr.Details["signOptions"])
} }
} else { } else {
leaf := certChain[0] leaf := certChain[0]
intermediate := certChain[1] intermediate := certChain[1]
if assert.Nil(t, tc.err) { if assert.Nil(t, tc.err) {
assert.Equals(t, leaf.NotBefore, tc.notBefore) assert.Equal(t, tc.notBefore, leaf.NotBefore)
assert.Equals(t, leaf.NotAfter, tc.notAfter) assert.Equal(t, tc.notAfter, leaf.NotAfter)
tmplt := a.config.AuthorityConfig.Template tmplt := a.config.AuthorityConfig.Template
if tc.csr.Subject.CommonName == "" { if tc.csr.Subject.CommonName == "" {
assert.Equals(t, leaf.Subject, pkix.Name{}) assert.Equal(t, pkix.Name{}, leaf.Subject)
} else { } else {
assert.Equals(t, leaf.Subject.String(), assert.Equal(t, pkix.Name{
pkix.Name{ Country: []string{tmplt.Country},
Country: []string{tmplt.Country}, Organization: []string{tmplt.Organization},
Organization: []string{tmplt.Organization}, Locality: []string{tmplt.Locality},
Locality: []string{tmplt.Locality}, StreetAddress: []string{tmplt.StreetAddress},
StreetAddress: []string{tmplt.StreetAddress}, Province: []string{tmplt.Province},
Province: []string{tmplt.Province}, CommonName: "smallstep test",
CommonName: "smallstep test", }.String(), leaf.Subject.String())
}.String()) assert.Equal(t, []string{"test.smallstep.com"}, leaf.DNSNames)
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com"})
} }
assert.Equals(t, leaf.Issuer, intermediate.Subject) assert.Equal(t, intermediate.Subject, leaf.Issuer)
assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm)
assert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA) assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm)
assert.Equals(t, leaf.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage)
issuer := getDefaultIssuer(a) issuer := getDefaultIssuer(a)
subjectKeyID, err := generateSubjectKeyID(pub) subjectKeyID, err := generateSubjectKeyID(pub)
assert.FatalError(t, err) require.NoError(t, err)
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) assert.Equal(t, subjectKeyID, leaf.SubjectKeyId)
assert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId)
// Verify Provisioner OID // Verify Provisioner OID
found := 0 found := 0
@ -900,18 +911,18 @@ ZYtQ9Ot36qc=
found++ found++
val := stepProvisionerASN1{} val := stepProvisionerASN1{}
_, err := asn1.Unmarshal(ext.Value, &val) _, err := asn1.Unmarshal(ext.Value, &val)
assert.FatalError(t, err) require.NoError(t, err)
assert.Equals(t, val.Type, provisionerTypeJWK) assert.Equal(t, provisionerTypeJWK, val.Type)
assert.Equals(t, val.Name, []byte(p.Name)) assert.Equal(t, []byte(p.Name), val.Name)
assert.Equals(t, val.CredentialID, []byte(p.Key.KeyID)) assert.Equal(t, []byte(p.Key.KeyID), val.CredentialID)
// Basic Constraints // Basic Constraints
case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 19})): case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 19})):
val := basicConstraints{} val := basicConstraints{}
_, err := asn1.Unmarshal(ext.Value, &val) _, err := asn1.Unmarshal(ext.Value, &val)
assert.FatalError(t, err) require.NoError(t, err)
assert.False(t, val.IsCA, false) assert.False(t, val.IsCA, false)
assert.Equals(t, val.MaxPathLen, 0) assert.Equal(t, val.MaxPathLen, 0)
// SAN extension // SAN extension
case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 17})): case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 17})):
@ -922,11 +933,11 @@ ZYtQ9Ot36qc=
} }
} }
} }
assert.Equals(t, found, 1) assert.Equal(t, found, 1)
realIntermediate, err := x509.ParseCertificate(issuer.Raw) realIntermediate, err := x509.ParseCertificate(issuer.Raw)
assert.FatalError(t, err) require.NoError(t, err)
assert.Equals(t, intermediate, realIntermediate) assert.Equal(t, realIntermediate, intermediate)
assert.Len(t, tc.extensionsCount, leaf.Extensions) assert.Len(t, leaf.Extensions, tc.extensionsCount)
} }
} }
}) })
@ -1056,7 +1067,7 @@ func TestAuthority_Renew(t *testing.T) {
for name, genTestCase := range tests { for name, genTestCase := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc, err := genTestCase() tc, err := genTestCase()
assert.FatalError(t, err) require.NoError(t, err)
var certChain []*x509.Certificate var certChain []*x509.Certificate
if tc.auth != nil { if tc.auth != nil {
@ -1068,19 +1079,19 @@ func TestAuthority_Renew(t *testing.T) {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
assert.Nil(t, certChain) assert.Nil(t, certChain)
var sc render.StatusCodedError var sc render.StatusCodedError
assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code) assert.Equal(t, tc.code, sc.StatusCode())
assert.HasPrefix(t, err.Error(), tc.err.Error()) assertHasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error var ctxErr *errs.Error
assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) assert.Equal(t, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"])
} }
} else { } else {
leaf := certChain[0] leaf := certChain[0]
intermediate := certChain[1] intermediate := certChain[1]
if assert.Nil(t, tc.err) { if assert.Nil(t, tc.err) {
assert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) assert.Equal(t, tc.cert.NotAfter.Sub(cert.NotBefore), leaf.NotAfter.Sub(leaf.NotBefore))
assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute))) assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute)))
assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute))) assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute)))
@ -1090,30 +1101,29 @@ func TestAuthority_Renew(t *testing.T) {
assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour))) assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour)))
tmplt := a.config.AuthorityConfig.Template tmplt := a.config.AuthorityConfig.Template
assert.Equals(t, leaf.RawSubject, tc.cert.RawSubject) assert.Equal(t, tc.cert.RawSubject, leaf.RawSubject)
assert.Equals(t, leaf.Subject.Country, []string{tmplt.Country}) assert.Equal(t, []string{tmplt.Country}, leaf.Subject.Country)
assert.Equals(t, leaf.Subject.Organization, []string{tmplt.Organization}) assert.Equal(t, []string{tmplt.Organization}, leaf.Subject.Organization)
assert.Equals(t, leaf.Subject.Locality, []string{tmplt.Locality}) assert.Equal(t, []string{tmplt.Locality}, leaf.Subject.Locality)
assert.Equals(t, leaf.Subject.StreetAddress, []string{tmplt.StreetAddress}) assert.Equal(t, []string{tmplt.StreetAddress}, leaf.Subject.StreetAddress)
assert.Equals(t, leaf.Subject.Province, []string{tmplt.Province}) assert.Equal(t, []string{tmplt.Province}, leaf.Subject.Province)
assert.Equals(t, leaf.Subject.CommonName, tmplt.CommonName) assert.Equal(t, tmplt.CommonName, leaf.Subject.CommonName)
assert.Equals(t, leaf.Issuer, intermediate.Subject) assert.Equal(t, intermediate.Subject, leaf.Issuer)
assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm)
assert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA) assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm)
assert.Equals(t, leaf.ExtKeyUsage, assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage)
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) assert.Equal(t, []string{"test.smallstep.com", "test"}, leaf.DNSNames)
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
subjectKeyID, err := generateSubjectKeyID(leaf.PublicKey) subjectKeyID, err := generateSubjectKeyID(leaf.PublicKey)
assert.FatalError(t, err) require.NoError(t, err)
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) assert.Equal(t, subjectKeyID, leaf.SubjectKeyId)
// We did not change the intermediate before renewing. // We did not change the intermediate before renewing.
authIssuer := getDefaultIssuer(tc.auth) authIssuer := getDefaultIssuer(tc.auth)
if issuer.SerialNumber == authIssuer.SerialNumber { if issuer.SerialNumber == authIssuer.SerialNumber {
assert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId)
// Compare extensions: they can be in a different order // Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions { for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier //skip SubjectKeyIdentifier
@ -1133,7 +1143,7 @@ func TestAuthority_Renew(t *testing.T) {
} }
} else { } else {
// We did change the intermediate before renewing. // We did change the intermediate before renewing.
assert.Equals(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) assert.Equal(t, authIssuer.SubjectKeyId, leaf.AuthorityKeyId)
// Compare extensions: they can be in a different order // Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions { for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier //skip SubjectKeyIdentifier
@ -1161,8 +1171,8 @@ func TestAuthority_Renew(t *testing.T) {
} }
realIntermediate, err := x509.ParseCertificate(authIssuer.Raw) realIntermediate, err := x509.ParseCertificate(authIssuer.Raw)
assert.FatalError(t, err) require.NoError(t, err)
assert.Equals(t, intermediate, realIntermediate) assert.Equal(t, realIntermediate, intermediate)
} }
} }
}) })
@ -1171,7 +1181,7 @@ func TestAuthority_Renew(t *testing.T) {
func TestAuthority_Rekey(t *testing.T) { func TestAuthority_Rekey(t *testing.T) {
pub, _, err := keyutil.GenerateDefaultKeyPair() pub, _, err := keyutil.GenerateDefaultKeyPair()
assert.FatalError(t, err) require.NoError(t, err)
a := testAuthority(t) a := testAuthority(t)
a.config.AuthorityConfig.Template = &ASN1DN{ a.config.AuthorityConfig.Template = &ASN1DN{
@ -1261,7 +1271,7 @@ func TestAuthority_Rekey(t *testing.T) {
for name, genTestCase := range tests { for name, genTestCase := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc, err := genTestCase() tc, err := genTestCase()
assert.FatalError(t, err) require.NoError(t, err)
var certChain []*x509.Certificate var certChain []*x509.Certificate
if tc.auth != nil { if tc.auth != nil {
@ -1273,19 +1283,19 @@ func TestAuthority_Rekey(t *testing.T) {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
assert.Nil(t, certChain) assert.Nil(t, certChain)
var sc render.StatusCodedError var sc render.StatusCodedError
assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code) assert.Equal(t, tc.code, sc.StatusCode())
assert.HasPrefix(t, err.Error(), tc.err.Error()) assertHasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error var ctxErr *errs.Error
assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) assert.Equal(t, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"])
} }
} else { } else {
leaf := certChain[0] leaf := certChain[0]
intermediate := certChain[1] intermediate := certChain[1]
if assert.Nil(t, tc.err) { if assert.Nil(t, tc.err) {
assert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) assert.Equal(t, tc.cert.NotAfter.Sub(cert.NotBefore), leaf.NotAfter.Sub(leaf.NotBefore))
assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute))) assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute)))
assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute))) assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute)))
@ -1295,41 +1305,39 @@ func TestAuthority_Rekey(t *testing.T) {
assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour))) assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour)))
tmplt := a.config.AuthorityConfig.Template tmplt := a.config.AuthorityConfig.Template
assert.Equals(t, leaf.Subject.String(), assert.Equal(t, pkix.Name{
pkix.Name{ Country: []string{tmplt.Country},
Country: []string{tmplt.Country}, Organization: []string{tmplt.Organization},
Organization: []string{tmplt.Organization}, Locality: []string{tmplt.Locality},
Locality: []string{tmplt.Locality}, StreetAddress: []string{tmplt.StreetAddress},
StreetAddress: []string{tmplt.StreetAddress}, Province: []string{tmplt.Province},
Province: []string{tmplt.Province}, CommonName: tmplt.CommonName,
CommonName: tmplt.CommonName, }.String(), leaf.Subject.String())
}.String()) assert.Equal(t, intermediate.Subject, leaf.Issuer)
assert.Equals(t, leaf.Issuer, intermediate.Subject)
assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm)
assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm)
assert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA) assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage)
assert.Equals(t, leaf.ExtKeyUsage, assert.Equal(t, []string{"test.smallstep.com", "test"}, leaf.DNSNames)
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
// Test Public Key and SubjectKeyId // Test Public Key and SubjectKeyId
expectedPK := tc.pk expectedPK := tc.pk
if tc.pk == nil { if tc.pk == nil {
expectedPK = cert.PublicKey expectedPK = cert.PublicKey
} }
assert.Equals(t, leaf.PublicKey, expectedPK) assert.Equal(t, expectedPK, leaf.PublicKey)
subjectKeyID, err := generateSubjectKeyID(expectedPK) subjectKeyID, err := generateSubjectKeyID(expectedPK)
assert.FatalError(t, err) require.NoError(t, err)
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) assert.Equal(t, subjectKeyID, leaf.SubjectKeyId)
if tc.pk == nil { if tc.pk == nil {
assert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId) assert.Equal(t, cert.SubjectKeyId, leaf.SubjectKeyId)
} }
// We did not change the intermediate before renewing. // We did not change the intermediate before renewing.
authIssuer := getDefaultIssuer(tc.auth) authIssuer := getDefaultIssuer(tc.auth)
if issuer.SerialNumber == authIssuer.SerialNumber { if issuer.SerialNumber == authIssuer.SerialNumber {
assert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId)
// Compare extensions: they can be in a different order // Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions { for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier //skip SubjectKeyIdentifier
@ -1349,7 +1357,7 @@ func TestAuthority_Rekey(t *testing.T) {
} }
} else { } else {
// We did change the intermediate before renewing. // We did change the intermediate before renewing.
assert.Equals(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) assert.Equal(t, authIssuer.SubjectKeyId, leaf.AuthorityKeyId)
// Compare extensions: they can be in a different order // Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions { for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier //skip SubjectKeyIdentifier
@ -1377,8 +1385,8 @@ func TestAuthority_Rekey(t *testing.T) {
} }
realIntermediate, err := x509.ParseCertificate(authIssuer.Raw) realIntermediate, err := x509.ParseCertificate(authIssuer.Raw)
assert.FatalError(t, err) require.NoError(t, err)
assert.Equals(t, intermediate, realIntermediate) assert.Equal(t, realIntermediate, intermediate)
} }
} }
}) })
@ -1413,10 +1421,10 @@ func TestAuthority_GetTLSOptions(t *testing.T) {
for name, genTestCase := range tests { for name, genTestCase := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc, err := genTestCase() tc, err := genTestCase()
assert.FatalError(t, err) require.NoError(t, err)
opts := tc.auth.GetTLSOptions() opts := tc.auth.GetTLSOptions()
assert.Equals(t, opts, tc.opts) assert.Equal(t, tc.opts, opts)
}) })
} }
} }
@ -1429,11 +1437,11 @@ func TestAuthority_Revoke(t *testing.T) {
now := time.Now().UTC() now := time.Now().UTC()
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass"))) jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err) require.NoError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID)) (&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID))
assert.FatalError(t, err) require.NoError(t, err)
a := testAuthority(t) a := testAuthority(t)
@ -1472,7 +1480,7 @@ func TestAuthority_Revoke(t *testing.T) {
ID: "44", ID: "44",
} }
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) require.NoError(t, err)
return test{ return test{
auth: a, auth: a,
@ -1486,9 +1494,9 @@ func TestAuthority_Revoke(t *testing.T) {
err: errors.New("authority.Revoke; no persistence layer configured"), err: errors.New("authority.Revoke; no persistence layer configured"),
code: http.StatusNotImplemented, code: http.StatusNotImplemented,
checkErrDetails: func(err *errs.Error) { checkErrDetails: func(err *errs.Error) {
assert.Equals(t, err.Details["token"], raw) assert.Equal(t, raw, err.Details["token"])
assert.Equals(t, err.Details["tokenID"], "44") assert.Equal(t, "44", err.Details["tokenID"])
assert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
}, },
} }
}, },
@ -1512,7 +1520,7 @@ func TestAuthority_Revoke(t *testing.T) {
ID: "44", ID: "44",
} }
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) require.NoError(t, err)
return test{ return test{
auth: _a, auth: _a,
@ -1526,9 +1534,9 @@ func TestAuthority_Revoke(t *testing.T) {
err: errors.New("authority.Revoke: force"), err: errors.New("authority.Revoke: force"),
code: http.StatusInternalServerError, code: http.StatusInternalServerError,
checkErrDetails: func(err *errs.Error) { checkErrDetails: func(err *errs.Error) {
assert.Equals(t, err.Details["token"], raw) assert.Equal(t, raw, err.Details["token"])
assert.Equals(t, err.Details["tokenID"], "44") assert.Equal(t, "44", err.Details["tokenID"])
assert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
}, },
} }
}, },
@ -1552,7 +1560,7 @@ func TestAuthority_Revoke(t *testing.T) {
ID: "44", ID: "44",
} }
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) require.NoError(t, err)
return test{ return test{
auth: _a, auth: _a,
@ -1566,9 +1574,9 @@ func TestAuthority_Revoke(t *testing.T) {
err: errors.New("certificate with serial number 'sn' is already revoked"), err: errors.New("certificate with serial number 'sn' is already revoked"),
code: http.StatusBadRequest, code: http.StatusBadRequest,
checkErrDetails: func(err *errs.Error) { checkErrDetails: func(err *errs.Error) {
assert.Equals(t, err.Details["token"], raw) assert.Equal(t, raw, err.Details["token"])
assert.Equals(t, err.Details["tokenID"], "44") assert.Equal(t, "44", err.Details["tokenID"])
assert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
}, },
} }
}, },
@ -1591,7 +1599,7 @@ func TestAuthority_Revoke(t *testing.T) {
ID: "44", ID: "44",
} }
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) require.NoError(t, err)
return test{ return test{
auth: _a, auth: _a,
ctx: tlsRevokeCtx, ctx: tlsRevokeCtx,
@ -1607,7 +1615,7 @@ func TestAuthority_Revoke(t *testing.T) {
_a := testAuthority(t, WithDatabase(&db.MockAuthDB{})) _a := testAuthority(t, WithDatabase(&db.MockAuthDB{}))
crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt") crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt")
assert.FatalError(t, err) require.NoError(t, err)
return test{ return test{
auth: _a, auth: _a,
@ -1625,7 +1633,7 @@ func TestAuthority_Revoke(t *testing.T) {
_a := testAuthority(t, WithDatabase(&db.MockAuthDB{})) _a := testAuthority(t, WithDatabase(&db.MockAuthDB{}))
crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt") crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt")
assert.FatalError(t, err) require.NoError(t, err)
// Filter out provisioner extension. // Filter out provisioner extension.
for i, ext := range crt.Extensions { for i, ext := range crt.Extensions {
if ext.Id.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1}) { if ext.Id.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1}) {
@ -1650,7 +1658,7 @@ func TestAuthority_Revoke(t *testing.T) {
_a := testAuthority(t, WithDatabase(&db.MockAuthDB{})) _a := testAuthority(t, WithDatabase(&db.MockAuthDB{}))
crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt") crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt")
assert.FatalError(t, err) require.NoError(t, err)
return test{ return test{
auth: _a, auth: _a,
@ -1683,7 +1691,7 @@ func TestAuthority_Revoke(t *testing.T) {
ID: "44", ID: "44",
} }
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) require.NoError(t, err)
return test{ return test{
auth: a, auth: a,
ctx: provisioner.NewContextWithMethod(context.Background(), provisioner.SSHRevokeMethod), ctx: provisioner.NewContextWithMethod(context.Background(), provisioner.SSHRevokeMethod),
@ -1702,17 +1710,17 @@ func TestAuthority_Revoke(t *testing.T) {
if err := tc.auth.Revoke(tc.ctx, tc.opts); err != nil { if err := tc.auth.Revoke(tc.ctx, tc.opts); err != nil {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
var sc render.StatusCodedError var sc render.StatusCodedError
assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code) assert.Equal(t, tc.code, sc.StatusCode())
assert.HasPrefix(t, err.Error(), tc.err.Error()) assertHasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error var ctxErr *errs.Error
assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equals(t, ctxErr.Details["serialNumber"], tc.opts.Serial) assert.Equal(t, tc.opts.Serial, ctxErr.Details["serialNumber"])
assert.Equals(t, ctxErr.Details["reasonCode"], tc.opts.ReasonCode) assert.Equal(t, tc.opts.ReasonCode, ctxErr.Details["reasonCode"])
assert.Equals(t, ctxErr.Details["reason"], tc.opts.Reason) assert.Equal(t, tc.opts.Reason, ctxErr.Details["reason"])
assert.Equals(t, ctxErr.Details["MTLS"], tc.opts.MTLS) assert.Equal(t, tc.opts.MTLS, ctxErr.Details["MTLS"])
assert.Equals(t, ctxErr.Details["context"], provisioner.RevokeMethod.String()) assert.Equal(t, provisioner.RevokeMethod.String(), ctxErr.Details["context"])
if tc.checkErrDetails != nil { if tc.checkErrDetails != nil {
tc.checkErrDetails(ctxErr) tc.checkErrDetails(ctxErr)
@ -1795,9 +1803,9 @@ func TestAuthority_constraints(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_, err = auth.Sign(csr, provisioner.SignOptions{}, templateOption) _, err = auth.SignWithContext(context.Background(), csr, provisioner.SignOptions{}, templateOption)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("Authority.Sign() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("Authority.SignWithContext() error = %v, wantErr %v", err, tt.wantErr)
} }
_, err = auth.Renew(cert) _, err = auth.Renew(cert)
@ -1814,13 +1822,11 @@ func TestAuthority_CRL(t *testing.T) {
validIssuer := "step-cli" validIssuer := "step-cli"
validAudience := testAudiences.Revoke validAudience := testAudiences.Revoke
now := time.Now().UTC() now := time.Now().UTC()
//
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass"))) jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err) require.NoError(t, err)
//
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID)) (&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID))
assert.FatalError(t, err) require.NoError(t, err)
crlCtx := provisioner.NewContextWithMethod(context.Background(), provisioner.RevokeMethod) crlCtx := provisioner.NewContextWithMethod(context.Background(), provisioner.RevokeMethod)
@ -1865,7 +1871,7 @@ func TestAuthority_CRL(t *testing.T) {
auth: a, auth: a,
ctx: crlCtx, ctx: crlCtx,
expected: nil, expected: nil,
err: database.ErrNotFound, err: errors.New("authority.GetCertificateRevocationList: not found"),
} }
}, },
"ok/crl-full": func() test { "ok/crl-full": func() test {
@ -1910,7 +1916,7 @@ func TestAuthority_CRL(t *testing.T) {
ID: sn, ID: sn,
} }
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) require.NoError(t, err)
err = a.Revoke(crlCtx, &RevokeOptions{ err = a.Revoke(crlCtx, &RevokeOptions{
Serial: sn, Serial: sn,
ReasonCode: reasonCode, ReasonCode: reasonCode,
@ -1918,7 +1924,7 @@ func TestAuthority_CRL(t *testing.T) {
OTT: raw, OTT: raw,
}) })
assert.FatalError(t, err) require.NoError(t, err)
ex = append(ex, sn) ex = append(ex, sn)
} }
@ -1933,22 +1939,58 @@ func TestAuthority_CRL(t *testing.T) {
for name, f := range tests { for name, f := range tests {
tc := f() tc := f()
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
if crlBytes, err := tc.auth.GetCertificateRevocationList(); err == nil { crlInfo, err := tc.auth.GetCertificateRevocationList()
crl, parseErr := x509.ParseRevocationList(crlBytes) if tc.err != nil {
if parseErr != nil { assert.EqualError(t, err, tc.err.Error())
t.Errorf("x509.ParseCertificateRequest() error = %v, wantErr %v", parseErr, nil) assert.Nil(t, crlInfo)
return return
} }
var cmpList []string crl, parseErr := x509.ParseRevocationList(crlInfo.Data)
for _, c := range crl.RevokedCertificates { require.NoError(t, parseErr)
cmpList = append(cmpList, c.SerialNumber.String())
}
assert.Equals(t, cmpList, tc.expected) var cmpList []string
} else { for _, c := range crl.RevokedCertificateEntries {
assert.NotNil(t, tc.err, err.Error()) cmpList = append(cmpList, c.SerialNumber.String())
} }
assert.Equal(t, tc.expected, cmpList)
})
}
}
type notImplementedCAS struct{}
func (notImplementedCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) {
return nil, apiv1.NotImplementedError{}
}
func (notImplementedCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) {
return nil, apiv1.NotImplementedError{}
}
func (notImplementedCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
return nil, apiv1.NotImplementedError{}
}
func TestAuthority_GetX509Signer(t *testing.T) {
auth := testAuthority(t)
require.IsType(t, &softcas.SoftCAS{}, auth.x509CAService)
signer := auth.x509CAService.(*softcas.SoftCAS).Signer
require.NotNil(t, signer)
tests := []struct {
name string
authority *Authority
want crypto.Signer
assertion assert.ErrorAssertionFunc
}{
{"ok", auth, signer, assert.NoError},
{"fail", testAuthority(t, WithX509CAService(notImplementedCAS{})), nil, assert.Error},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.authority.GetX509Signer()
tt.assertion(t, err)
assert.Equal(t, tt.want, got)
}) })
} }
} }

@ -1,8 +1,12 @@
package authority package authority
import "github.com/smallstep/certificates/webhook" import (
"context"
"github.com/smallstep/certificates/webhook"
)
type webhookController interface { type webhookController interface {
Enrich(*webhook.RequestBody) error Enrich(context.Context, *webhook.RequestBody) error
Authorize(*webhook.RequestBody) error Authorize(context.Context, *webhook.RequestBody) error
} }

@ -1,6 +1,8 @@
package authority package authority
import ( import (
"context"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/webhook" "github.com/smallstep/certificates/webhook"
) )
@ -14,7 +16,7 @@ type mockWebhookController struct {
var _ webhookController = &mockWebhookController{} var _ webhookController = &mockWebhookController{}
func (wc *mockWebhookController) Enrich(*webhook.RequestBody) error { func (wc *mockWebhookController) Enrich(context.Context, *webhook.RequestBody) error {
for key, data := range wc.respData { for key, data := range wc.respData {
wc.templateData.SetWebhook(key, data) wc.templateData.SetWebhook(key, data)
} }
@ -22,6 +24,6 @@ func (wc *mockWebhookController) Enrich(*webhook.RequestBody) error {
return wc.enrichErr return wc.enrichErr
} }
func (wc *mockWebhookController) Authorize(*webhook.RequestBody) error { func (wc *mockWebhookController) Authorize(context.Context, *webhook.RequestBody) error {
return wc.authorizeErr return wc.authorizeErr
} }

@ -48,6 +48,7 @@ func NewACMEClient(endpoint string, contact []string, opts ...ClientOption) (*AC
return nil, errors.Wrapf(err, "creating GET request %s failed", endpoint) return nil, errors.Wrapf(err, "creating GET request %s failed", endpoint)
} }
req.Header.Set("User-Agent", UserAgent) req.Header.Set("User-Agent", UserAgent)
enforceRequestID(req)
resp, err := ac.client.Do(req) resp, err := ac.client.Do(req)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", endpoint) return nil, errors.Wrapf(err, "client GET %s failed", endpoint)
@ -109,6 +110,7 @@ func (c *ACMEClient) GetNonce() (string, error) {
return "", errors.Wrapf(err, "creating GET request %s failed", c.dir.NewNonce) return "", errors.Wrapf(err, "creating GET request %s failed", c.dir.NewNonce)
} }
req.Header.Set("User-Agent", UserAgent) req.Header.Set("User-Agent", UserAgent)
enforceRequestID(req)
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return "", errors.Wrapf(err, "client GET %s failed", c.dir.NewNonce) return "", errors.Wrapf(err, "client GET %s failed", c.dir.NewNonce)
@ -188,6 +190,7 @@ func (c *ACMEClient) post(payload []byte, url string, headerOps ...withHeaderOpt
} }
req.Header.Set("Content-Type", "application/jose+json") req.Header.Set("Content-Type", "application/jose+json")
req.Header.Set("User-Agent", UserAgent) req.Header.Set("User-Agent", UserAgent)
enforceRequestID(req)
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", c.dir.NewOrder) return nil, errors.Wrapf(err, "client POST %s failed", c.dir.NewOrder)

@ -108,19 +108,19 @@ func TestNewACMEClient(t *testing.T) {
tc := run(t) tc := run(t)
i := 0 i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
switch { switch {
case i == 0: case i == 0:
render.JSONStatus(w, tc.r1, tc.rc1) render.JSONStatus(w, r, tc.r1, tc.rc1)
i++ i++
case i == 1: case i == 1:
w.Header().Set("Replay-Nonce", "abc123") w.Header().Set("Replay-Nonce", "abc123")
render.JSONStatus(w, []byte{}, 200) render.JSONStatus(w, r, []byte{}, 200)
i++ i++
default: default:
w.Header().Set("Location", accLocation) w.Header().Set("Location", accLocation)
render.JSONStatus(w, tc.r2, tc.rc2) render.JSONStatus(w, r, tc.r2, tc.rc2)
} }
}) })
@ -203,10 +203,10 @@ func TestACMEClient_GetNonce(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := run(t) tc := run(t)
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce) w.Header().Set("Replay-Nonce", expectedNonce)
render.JSONStatus(w, tc.r1, tc.rc1) render.JSONStatus(w, r, tc.r1, tc.rc1)
}) })
if nonce, err := ac.GetNonce(); err != nil { if nonce, err := ac.GetNonce(); err != nil {
@ -310,18 +310,18 @@ func TestACMEClient_post(t *testing.T) {
tc := run(t) tc := run(t)
i := 0 i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce) w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 { if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1) render.JSONStatus(w, r, tc.r1, tc.rc1)
i++ i++
return return
} }
// validate jws request protected headers and body // validate jws request protected headers and body
body, err := io.ReadAll(req.Body) body, err := io.ReadAll(r.Body)
assert.FatalError(t, err) assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body)) jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -338,7 +338,7 @@ func TestACMEClient_post(t *testing.T) {
assert.Equals(t, hdr.KeyID, ac.kid) assert.Equals(t, hdr.KeyID, ac.kid)
} }
render.JSONStatus(w, tc.r2, tc.rc2) render.JSONStatus(w, r, tc.r2, tc.rc2)
}) })
if resp, err := tc.client.post(tc.payload, url, tc.ops...); err != nil { if resp, err := tc.client.post(tc.payload, url, tc.ops...); err != nil {
@ -450,18 +450,18 @@ func TestACMEClient_NewOrder(t *testing.T) {
tc := run(t) tc := run(t)
i := 0 i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce) w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 { if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1) render.JSONStatus(w, r, tc.r1, tc.rc1)
i++ i++
return return
} }
// validate jws request protected headers and body // validate jws request protected headers and body
body, err := io.ReadAll(req.Body) body, err := io.ReadAll(r.Body)
assert.FatalError(t, err) assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body)) jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -477,7 +477,7 @@ func TestACMEClient_NewOrder(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, payload, norb) assert.Equals(t, payload, norb)
render.JSONStatus(w, tc.r2, tc.rc2) render.JSONStatus(w, r, tc.r2, tc.rc2)
}) })
if res, err := ac.NewOrder(norb); err != nil { if res, err := ac.NewOrder(norb); err != nil {
@ -572,18 +572,18 @@ func TestACMEClient_GetOrder(t *testing.T) {
tc := run(t) tc := run(t)
i := 0 i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce) w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 { if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1) render.JSONStatus(w, r, tc.r1, tc.rc1)
i++ i++
return return
} }
// validate jws request protected headers and body // validate jws request protected headers and body
body, err := io.ReadAll(req.Body) body, err := io.ReadAll(r.Body)
assert.FatalError(t, err) assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body)) jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -599,7 +599,7 @@ func TestACMEClient_GetOrder(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, len(payload), 0) assert.Equals(t, len(payload), 0)
render.JSONStatus(w, tc.r2, tc.rc2) render.JSONStatus(w, r, tc.r2, tc.rc2)
}) })
if res, err := ac.GetOrder(url); err != nil { if res, err := ac.GetOrder(url); err != nil {
@ -694,18 +694,18 @@ func TestACMEClient_GetAuthz(t *testing.T) {
tc := run(t) tc := run(t)
i := 0 i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce) w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 { if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1) render.JSONStatus(w, r, tc.r1, tc.rc1)
i++ i++
return return
} }
// validate jws request protected headers and body // validate jws request protected headers and body
body, err := io.ReadAll(req.Body) body, err := io.ReadAll(r.Body)
assert.FatalError(t, err) assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body)) jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -721,7 +721,7 @@ func TestACMEClient_GetAuthz(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, len(payload), 0) assert.Equals(t, len(payload), 0)
render.JSONStatus(w, tc.r2, tc.rc2) render.JSONStatus(w, r, tc.r2, tc.rc2)
}) })
if res, err := ac.GetAuthz(url); err != nil { if res, err := ac.GetAuthz(url); err != nil {
@ -816,18 +816,18 @@ func TestACMEClient_GetChallenge(t *testing.T) {
tc := run(t) tc := run(t)
i := 0 i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce) w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 { if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1) render.JSONStatus(w, r, tc.r1, tc.rc1)
i++ i++
return return
} }
// validate jws request protected headers and body // validate jws request protected headers and body
body, err := io.ReadAll(req.Body) body, err := io.ReadAll(r.Body)
assert.FatalError(t, err) assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body)) jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -844,7 +844,7 @@ func TestACMEClient_GetChallenge(t *testing.T) {
assert.Equals(t, len(payload), 0) assert.Equals(t, len(payload), 0)
render.JSONStatus(w, tc.r2, tc.rc2) render.JSONStatus(w, r, tc.r2, tc.rc2)
}) })
if res, err := ac.GetChallenge(url); err != nil { if res, err := ac.GetChallenge(url); err != nil {
@ -939,18 +939,18 @@ func TestACMEClient_ValidateChallenge(t *testing.T) {
tc := run(t) tc := run(t)
i := 0 i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce) w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 { if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1) render.JSONStatus(w, r, tc.r1, tc.rc1)
i++ i++
return return
} }
// validate jws request protected headers and body // validate jws request protected headers and body
body, err := io.ReadAll(req.Body) body, err := io.ReadAll(r.Body)
assert.FatalError(t, err) assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body)) jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -967,7 +967,7 @@ func TestACMEClient_ValidateChallenge(t *testing.T) {
assert.Equals(t, payload, []byte("{}")) assert.Equals(t, payload, []byte("{}"))
render.JSONStatus(w, tc.r2, tc.rc2) render.JSONStatus(w, r, tc.r2, tc.rc2)
}) })
if err := ac.ValidateChallenge(url); err != nil { if err := ac.ValidateChallenge(url); err != nil {
@ -983,22 +983,22 @@ func TestACMEClient_ValidateWithPayload(t *testing.T) {
key, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) key, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err) assert.FatalError(t, err)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
t.Log(req.RequestURI) t.Log(r.RequestURI)
w.Header().Set("Replay-Nonce", "nonce") w.Header().Set("Replay-Nonce", "nonce")
switch req.RequestURI { switch r.RequestURI {
case "/nonce": case "/nonce":
render.JSONStatus(w, []byte{}, 200) render.JSONStatus(w, r, []byte{}, 200)
return return
case "/fail-nonce": case "/fail-nonce":
render.JSONStatus(w, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400) render.JSONStatus(w, r, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400)
return return
} }
// validate jws request protected headers and body // validate jws request protected headers and body
body, err := io.ReadAll(req.Body) body, err := io.ReadAll(r.Body)
assert.FatalError(t, err) assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body)) jws, err := jose.ParseJWS(string(body))
@ -1015,15 +1015,15 @@ func TestACMEClient_ValidateWithPayload(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, payload, []byte("the-payload")) assert.Equals(t, payload, []byte("the-payload"))
switch req.RequestURI { switch r.RequestURI {
case "/ok": case "/ok":
render.JSONStatus(w, acme.Challenge{ render.JSONStatus(w, r, acme.Challenge{
Type: "device-attestation-01", Type: "device-attestation-01",
Status: "valid", Status: "valid",
Token: "foo", Token: "foo",
}, 200) }, 200)
case "/fail": case "/fail":
render.JSONStatus(w, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400) render.JSONStatus(w, r, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400)
} }
})) }))
defer srv.Close() defer srv.Close()
@ -1160,18 +1160,18 @@ func TestACMEClient_FinalizeOrder(t *testing.T) {
tc := run(t) tc := run(t)
i := 0 i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce) w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 { if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1) render.JSONStatus(w, r, tc.r1, tc.rc1)
i++ i++
return return
} }
// validate jws request protected headers and body // validate jws request protected headers and body
body, err := io.ReadAll(req.Body) body, err := io.ReadAll(r.Body)
assert.FatalError(t, err) assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body)) jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -1187,7 +1187,7 @@ func TestACMEClient_FinalizeOrder(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, payload, frb) assert.Equals(t, payload, frb)
render.JSONStatus(w, tc.r2, tc.rc2) render.JSONStatus(w, r, tc.r2, tc.rc2)
}) })
if err := ac.FinalizeOrder(url, csr); err != nil { if err := ac.FinalizeOrder(url, csr); err != nil {
@ -1289,18 +1289,18 @@ func TestACMEClient_GetAccountOrders(t *testing.T) {
tc := run(t) tc := run(t)
i := 0 i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce) w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 { if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1) render.JSONStatus(w, r, tc.r1, tc.rc1)
i++ i++
return return
} }
// validate jws request protected headers and body // validate jws request protected headers and body
body, err := io.ReadAll(req.Body) body, err := io.ReadAll(r.Body)
assert.FatalError(t, err) assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body)) jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -1316,7 +1316,7 @@ func TestACMEClient_GetAccountOrders(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, len(payload), 0) assert.Equals(t, len(payload), 0)
render.JSONStatus(w, tc.r2, tc.rc2) render.JSONStatus(w, r, tc.r2, tc.rc2)
}) })
if res, err := tc.client.GetAccountOrders(); err != nil { if res, err := tc.client.GetAccountOrders(); err != nil {
@ -1420,18 +1420,18 @@ func TestACMEClient_GetCertificate(t *testing.T) {
tc := run(t) tc := run(t)
i := 0 i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce) w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 { if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1) render.JSONStatus(w, r, tc.r1, tc.rc1)
i++ i++
return return
} }
// validate jws request protected headers and body // validate jws request protected headers and body
body, err := io.ReadAll(req.Body) body, err := io.ReadAll(r.Body)
assert.FatalError(t, err) assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body)) jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -1450,7 +1450,7 @@ func TestACMEClient_GetCertificate(t *testing.T) {
if tc.certBytes != nil { if tc.certBytes != nil {
w.Write(tc.certBytes) w.Write(tc.certBytes)
} else { } else {
render.JSONStatus(w, tc.r2, tc.rc2) render.JSONStatus(w, r, tc.r2, tc.rc2)
} }
}) })

@ -204,7 +204,7 @@ func (o *adminOptions) apply(opts []AdminOption) (err error) {
func (o *adminOptions) rawQuery() string { func (o *adminOptions) rawQuery() string {
v := url.Values{} v := url.Values{}
if len(o.cursor) > 0 { if o.cursor != "" {
v.Set("cursor", o.cursor) v.Set("cursor", o.cursor)
} }
if o.limit > 0 { if o.limit > 0 {

@ -87,7 +87,7 @@ func startCAServer(configFile string) (*CA, string, error) {
func mTLSMiddleware(next http.Handler, nonAuthenticatedPaths ...string) http.Handler { func mTLSMiddleware(next http.Handler, nonAuthenticatedPaths ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/version" { if r.URL.Path == "/version" {
render.JSON(w, api.VersionResponse{ render.JSON(w, r, api.VersionResponse{
Version: "test", Version: "test",
RequireClientAuthentication: true, RequireClientAuthentication: true,
}) })
@ -102,7 +102,7 @@ func mTLSMiddleware(next http.Handler, nonAuthenticatedPaths ...string) http.Han
} }
isMTLS := r.TLS != nil && len(r.TLS.PeerCertificates) > 0 isMTLS := r.TLS != nil && len(r.TLS.PeerCertificates) > 0
if !isMTLS { if !isMTLS {
render.Error(w, errs.Unauthorized("missing peer certificate")) render.Error(w, r, errs.Unauthorized("missing peer certificate"))
} else { } else {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }
@ -412,7 +412,7 @@ func TestBootstrapClientServerRotation(t *testing.T) {
//nolint:gosec // insecure test server //nolint:gosec // insecure test server
server, err := BootstrapServer(context.Background(), token, &http.Server{ server, err := BootstrapServer(context.Background(), token, &http.Server{
Addr: ":0", Addr: ":0",
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok")) w.Write([]byte("ok"))
}), }),
}, RequireAndVerifyClientCert()) }, RequireAndVerifyClientCert())
@ -531,7 +531,7 @@ func TestBootstrapClientServerFederation(t *testing.T) {
//nolint:gosec // insecure test server //nolint:gosec // insecure test server
server, err := BootstrapServer(context.Background(), token, &http.Server{ server, err := BootstrapServer(context.Background(), token, &http.Server{
Addr: ":0", Addr: ":0",
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok")) w.Write([]byte("ok"))
}), }),
}, RequireAndVerifyClientCert(), AddFederationToClientCAs()) }, RequireAndVerifyClientCert(), AddFederationToClientCAs())

@ -26,8 +26,11 @@ import (
"github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/admin"
adminAPI "github.com/smallstep/certificates/authority/admin/api" adminAPI "github.com/smallstep/certificates/authority/admin/api"
"github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/internal/metrix"
"github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/middleware/requestid"
"github.com/smallstep/certificates/monitoring" "github.com/smallstep/certificates/monitoring"
"github.com/smallstep/certificates/scep" "github.com/smallstep/certificates/scep"
scepAPI "github.com/smallstep/certificates/scep/api" scepAPI "github.com/smallstep/certificates/scep/api"
@ -46,6 +49,8 @@ type options struct {
sshHostPassword []byte sshHostPassword []byte
sshUserPassword []byte sshUserPassword []byte
database db.AuthDB database db.AuthDB
x509CAService apiv1.CertificateAuthorityService
tlsConfig *tls.Config
} }
func (o *options) apply(opts []Option) { func (o *options) apply(opts []Option) {
@ -65,6 +70,13 @@ func WithConfigFile(name string) Option {
} }
} }
// WithX509CAService provides the x509CAService to be used for signing x509 requests
func WithX509CAService(svc apiv1.CertificateAuthorityService) Option {
return func(o *options) {
o.x509CAService = svc
}
}
// WithPassword sets the given password as the configured password in the CA // WithPassword sets the given password as the configured password in the CA
// options. // options.
func WithPassword(password []byte) Option { func WithPassword(password []byte) Option {
@ -104,6 +116,14 @@ func WithDatabase(d db.AuthDB) Option {
} }
} }
// WithTLSConfig sets the TLS configuration to be used by the HTTP(s) server
// spun by step-ca.
func WithTLSConfig(t *tls.Config) Option {
return func(o *options) {
o.tlsConfig = t
}
}
// WithLinkedCAToken sets the token used to authenticate with the linkedca. // WithLinkedCAToken sets the token used to authenticate with the linkedca.
func WithLinkedCAToken(token string) Option { func WithLinkedCAToken(token string) Option {
return func(o *options) { return func(o *options) {
@ -125,6 +145,7 @@ type CA struct {
config *config.Config config *config.Config
srv *server.Server srv *server.Server
insecureSrv *server.Server insecureSrv *server.Server
metricsSrv *server.Server
opts *options opts *options
renewer *TLSRenewer renewer *TLSRenewer
compactStop chan struct{} compactStop chan struct{}
@ -163,6 +184,16 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
opts = append(opts, authority.WithQuietInit()) opts = append(opts, authority.WithQuietInit())
} }
if ca.opts.x509CAService != nil {
opts = append(opts, authority.WithX509CAService(ca.opts.x509CAService))
}
var meter *metrix.Meter
if ca.config.MetricsAddress != "" {
meter = metrix.New()
opts = append(opts, authority.WithMeter(meter))
}
webhookTransport := http.DefaultTransport.(*http.Transport).Clone() webhookTransport := http.DefaultTransport.(*http.Transport).Clone()
opts = append(opts, authority.WithWebhookClient(&http.Client{Transport: webhookTransport})) opts = append(opts, authority.WithWebhookClient(&http.Client{Transport: webhookTransport}))
@ -172,9 +203,20 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
} }
ca.auth = auth ca.auth = auth
tlsConfig, clientTLSConfig, err := ca.getTLSConfig(auth) var tlsConfig *tls.Config
if err != nil { var clientTLSConfig *tls.Config
return nil, err if ca.opts.tlsConfig != nil {
// try using the tls Configuration supplied by the caller
log.Print("Using tls configuration supplied by the application")
tlsConfig = ca.opts.tlsConfig
clientTLSConfig = ca.opts.tlsConfig
} else {
// default to using the step-ca x509 Signer Interface
log.Print("Building new tls configuration using step-ca x509 Signer Interface")
tlsConfig, clientTLSConfig, err = ca.getTLSConfig(auth)
if err != nil {
return nil, err
}
} }
webhookTransport.TLSClientConfig = clientTLSConfig webhookTransport.TLSClientConfig = clientTLSConfig
@ -288,15 +330,21 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
} }
// Add logger if configured // Add logger if configured
var legacyTraceHeader string
if len(cfg.Logger) > 0 { if len(cfg.Logger) > 0 {
logger, err := logging.New("ca", cfg.Logger) logger, err := logging.New("ca", cfg.Logger)
if err != nil { if err != nil {
return nil, err return nil, err
} }
legacyTraceHeader = logger.GetTraceHeader()
handler = logger.Middleware(handler) handler = logger.Middleware(handler)
insecureHandler = logger.Middleware(insecureHandler) insecureHandler = logger.Middleware(insecureHandler)
} }
// always use request ID middleware; traceHeader is provided for backwards compatibility (for now)
handler = requestid.New(legacyTraceHeader).Middleware(handler)
insecureHandler = requestid.New(legacyTraceHeader).Middleware(insecureHandler)
// Create context with all the necessary values. // Create context with all the necessary values.
baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker) baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker)
@ -318,6 +366,13 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
} }
} }
if meter != nil {
ca.metricsSrv = server.New(ca.config.MetricsAddress, meter, nil)
ca.metricsSrv.BaseContext = func(net.Listener) context.Context {
return baseContext
}
}
return ca, nil return ca, nil
} }
@ -404,6 +459,14 @@ func (ca *CA) Run() error {
}() }()
} }
if ca.metricsSrv != nil {
wg.Add(1)
go func() {
defer wg.Done()
errs <- ca.metricsSrv.ListenAndServe()
}()
}
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
@ -413,6 +476,20 @@ func (ca *CA) Run() error {
// wait till error occurs; ensures the servers keep listening // wait till error occurs; ensures the servers keep listening
err := <-errs err := <-errs
// if the error is not the usual HTTP server closed error, it is
// highly likely that an error occurred when starting one of the
// CA servers, possibly because of a port already being in use or
// some part of the configuration not being correct. This case is
// handled by stopping the CA in its entirety.
if !errors.Is(err, http.ErrServerClosed) {
log.Println("shutting down due to startup error ...")
if stopErr := ca.Stop(); stopErr != nil {
err = fmt.Errorf("failed stopping CA after error occurred: %w: %w", err, stopErr)
} else {
err = fmt.Errorf("stopped CA after error occurred: %w", err)
}
}
wg.Wait() wg.Wait()
return err return err
@ -421,7 +498,10 @@ func (ca *CA) Run() error {
// Stop stops the CA calling to the server Shutdown method. // Stop stops the CA calling to the server Shutdown method.
func (ca *CA) Stop() error { func (ca *CA) Stop() error {
close(ca.compactStop) close(ca.compactStop)
ca.renewer.Stop() if ca.renewer != nil {
ca.renewer.Stop()
}
if err := ca.auth.Shutdown(); err != nil { if err := ca.auth.Shutdown(); err != nil {
log.Printf("error stopping ca.Authority: %+v\n", err) log.Printf("error stopping ca.Authority: %+v\n", err)
} }
@ -480,6 +560,13 @@ func (ca *CA) Reload() error {
} }
} }
if ca.metricsSrv != nil {
if err = ca.metricsSrv.Reload(newCA.metricsSrv); err != nil {
logContinue("Reload failed because metrics server could not be replaced.")
return errors.Wrap(err, "error reloading metrics server")
}
}
if err = ca.srv.Reload(newCA.srv); err != nil { if err = ca.srv.Reload(newCA.srv); err != nil {
logContinue("Reload failed because server could not be replaced.") logContinue("Reload failed because server could not be replaced.")
return errors.Wrap(err, "error reloading server") return errors.Wrap(err, "error reloading server")
@ -489,7 +576,10 @@ func (ca *CA) Reload() error {
// 2. Safely shutdown any internal resources (e.g. key manager) // 2. Safely shutdown any internal resources (e.g. key manager)
// 3. Replace ca properties // 3. Replace ca properties
// Do not replace ca.srv // Do not replace ca.srv
ca.renewer.Stop() if ca.renewer != nil {
ca.renewer.Stop()
}
ca.auth.CloseForReload() ca.auth.CloseForReload()
ca.auth = newCA.auth ca.auth = newCA.auth
ca.config = newCA.config ca.config = newCA.config
@ -588,7 +678,7 @@ func (ca *CA) shouldServeSCEPEndpoints() bool {
//nolint:unused // useful for debugging //nolint:unused // useful for debugging
func dumpRoutes(mux chi.Routes) { func dumpRoutes(mux chi.Routes) {
// helpful routine for logging all routes // helpful routine for logging all routes
walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { walkFunc := func(method string, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error {
fmt.Printf("%s %s\n", method, route) fmt.Printf("%s %s\n", method, route)
return nil return nil
} }

@ -289,6 +289,9 @@ ZEp7knvU2psWRw==
if assert.Equals(t, rr.Code, tc.status) { if assert.Equals(t, rr.Code, tc.status) {
body := &ClosingBuffer{rr.Body} body := &ClosingBuffer{rr.Body}
resp := &http.Response{
Body: body,
}
if rr.Code < http.StatusBadRequest { if rr.Code < http.StatusBadRequest {
var sign api.SignResponse var sign api.SignResponse
assert.FatalError(t, readJSON(body, &sign)) assert.FatalError(t, readJSON(body, &sign))
@ -325,7 +328,7 @@ ZEp7knvU2psWRw==
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, intermediate, realIntermediate) assert.Equals(t, intermediate, realIntermediate)
} else { } else {
err := readError(body) err := readError(resp)
if tc.errMsg == "" { if tc.errMsg == "" {
assert.FatalError(t, errors.New("must validate response error")) assert.FatalError(t, errors.New("must validate response error"))
} }
@ -369,6 +372,9 @@ func TestCAProvisioners(t *testing.T) {
if assert.Equals(t, rr.Code, tc.status) { if assert.Equals(t, rr.Code, tc.status) {
body := &ClosingBuffer{rr.Body} body := &ClosingBuffer{rr.Body}
resp := &http.Response{
Body: body,
}
if rr.Code < http.StatusBadRequest { if rr.Code < http.StatusBadRequest {
var resp api.ProvisionersResponse var resp api.ProvisionersResponse
@ -379,7 +385,7 @@ func TestCAProvisioners(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, a, b) assert.Equals(t, a, b)
} else { } else {
err := readError(body) err := readError(resp)
if tc.errMsg == "" { if tc.errMsg == "" {
assert.FatalError(t, errors.New("must validate response error")) assert.FatalError(t, errors.New("must validate response error"))
} }
@ -436,12 +442,15 @@ func TestCAProvisionerEncryptedKey(t *testing.T) {
if assert.Equals(t, rr.Code, tc.status) { if assert.Equals(t, rr.Code, tc.status) {
body := &ClosingBuffer{rr.Body} body := &ClosingBuffer{rr.Body}
resp := &http.Response{
Body: body,
}
if rr.Code < http.StatusBadRequest { if rr.Code < http.StatusBadRequest {
var ek api.ProvisionerKeyResponse var ek api.ProvisionerKeyResponse
assert.FatalError(t, readJSON(body, &ek)) assert.FatalError(t, readJSON(body, &ek))
assert.Equals(t, ek.Key, tc.expectedKey) assert.Equals(t, ek.Key, tc.expectedKey)
} else { } else {
err := readError(body) err := readError(resp)
if tc.errMsg == "" { if tc.errMsg == "" {
assert.FatalError(t, errors.New("must validate response error")) assert.FatalError(t, errors.New("must validate response error"))
} }
@ -498,12 +507,15 @@ func TestCARoot(t *testing.T) {
if assert.Equals(t, rr.Code, tc.status) { if assert.Equals(t, rr.Code, tc.status) {
body := &ClosingBuffer{rr.Body} body := &ClosingBuffer{rr.Body}
resp := &http.Response{
Body: body,
}
if rr.Code < http.StatusBadRequest { if rr.Code < http.StatusBadRequest {
var root api.RootResponse var root api.RootResponse
assert.FatalError(t, readJSON(body, &root)) assert.FatalError(t, readJSON(body, &root))
assert.Equals(t, root.RootPEM.Certificate, rootCrt) assert.Equals(t, root.RootPEM.Certificate, rootCrt)
} else { } else {
err := readError(body) err := readError(resp)
if tc.errMsg == "" { if tc.errMsg == "" {
assert.FatalError(t, errors.New("must validate response error")) assert.FatalError(t, errors.New("must validate response error"))
} }
@ -641,6 +653,9 @@ func TestCARenew(t *testing.T) {
if assert.Equals(t, rr.Code, tc.status) { if assert.Equals(t, rr.Code, tc.status) {
body := &ClosingBuffer{rr.Body} body := &ClosingBuffer{rr.Body}
resp := &http.Response{
Body: body,
}
if rr.Code < http.StatusBadRequest { if rr.Code < http.StatusBadRequest {
var sign api.SignResponse var sign api.SignResponse
assert.FatalError(t, readJSON(body, &sign)) assert.FatalError(t, readJSON(body, &sign))
@ -673,7 +688,7 @@ func TestCARenew(t *testing.T) {
assert.Equals(t, *sign.TLSOptions, authority.DefaultTLSOptions) assert.Equals(t, *sign.TLSOptions, authority.DefaultTLSOptions)
} else { } else {
err := readError(body) err := readError(resp)
if tc.errMsg == "" { if tc.errMsg == "" {
assert.FatalError(t, errors.New("must validate response error")) assert.FatalError(t, errors.New("must validate response error"))
} }

@ -27,12 +27,14 @@ import (
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/ca/client"
"github.com/smallstep/certificates/ca/identity" "github.com/smallstep/certificates/ca/identity"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"go.step.sm/cli-utils/step" "go.step.sm/cli-utils/step"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil" "go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/crypto/randutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protojson"
@ -83,8 +85,7 @@ func (c *uaClient) GetWithContext(ctx context.Context, u string) (*http.Response
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "create GET %s request failed", u) return nil, errors.Wrapf(err, "create GET %s request failed", u)
} }
req.Header.Set("User-Agent", UserAgent) return c.Do(req)
return c.Client.Do(req)
} }
func (c *uaClient) Post(u, contentType string, body io.Reader) (*http.Response, error) { func (c *uaClient) Post(u, contentType string, body io.Reader) (*http.Response, error) {
@ -97,12 +98,43 @@ func (c *uaClient) PostWithContext(ctx context.Context, u, contentType string, b
return nil, errors.Wrapf(err, "create POST %s request failed", u) return nil, errors.Wrapf(err, "create POST %s request failed", u)
} }
req.Header.Set("Content-Type", contentType) req.Header.Set("Content-Type", contentType)
req.Header.Set("User-Agent", UserAgent) return c.Do(req)
return c.Client.Do(req) }
// requestIDHeader is the header name used for propagating request IDs from
// the CA client to the CA and back again.
const requestIDHeader = "X-Request-Id"
// newRequestID generates a new random UUIDv4 request ID. If it fails,
// the request ID will be the empty string.
func newRequestID() string {
requestID, err := randutil.UUIDv4()
if err != nil {
return ""
}
return requestID
}
// enforceRequestID checks if the X-Request-Id HTTP header is filled. If it's
// empty, the context is searched for a request ID. If that's also empty, a new
// request ID is generated.
func enforceRequestID(r *http.Request) {
if requestID := r.Header.Get(requestIDHeader); requestID == "" {
if reqID, ok := client.RequestIDFromContext(r.Context()); ok {
// TODO(hs): ensure the request ID from the context is fresh, and thus hasn't been
// used before by the client (unless it's a retry for the same request)?
requestID = reqID
} else {
requestID = newRequestID()
}
r.Header.Set(requestIDHeader, requestID)
}
} }
func (c *uaClient) Do(req *http.Request) (*http.Response, error) { func (c *uaClient) Do(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", UserAgent) req.Header.Set("User-Agent", UserAgent)
enforceRequestID(req)
return c.Client.Do(req) return c.Client.Do(req)
} }
@ -375,8 +407,8 @@ func getTransportFromSHA256(endpoint, sum string) (http.RoundTripper, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
client := &Client{endpoint: u} caClient := &Client{endpoint: u}
root, err := client.Root(sum) root, err := caClient.Root(sum)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -610,7 +642,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var version api.VersionResponse var version api.VersionResponse
if err := readJSON(resp.Body, &version); err != nil { if err := readJSON(resp.Body, &version); err != nil {
@ -640,7 +672,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var health api.HealthResponse var health api.HealthResponse
if err := readJSON(resp.Body, &health); err != nil { if err := readJSON(resp.Body, &health); err != nil {
@ -675,7 +707,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var root api.RootResponse var root api.RootResponse
if err := readJSON(resp.Body, &root); err != nil { if err := readJSON(resp.Body, &root); err != nil {
@ -714,7 +746,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var sign api.SignResponse var sign api.SignResponse
if err := readJSON(resp.Body, &sign); err != nil { if err := readJSON(resp.Body, &sign); err != nil {
@ -737,14 +769,14 @@ func (c *Client) Renew(tr http.RoundTripper) (*api.SignResponse, error) {
func (c *Client) RenewWithContext(ctx context.Context, tr http.RoundTripper) (*api.SignResponse, error) { func (c *Client) RenewWithContext(ctx context.Context, tr http.RoundTripper) (*api.SignResponse, error) {
var retried bool var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/renew"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/renew"})
client := &http.Client{Transport: tr} httpClient := &http.Client{Transport: tr}
retry: retry:
req, err := http.NewRequestWithContext(ctx, "POST", u.String(), http.NoBody) req, err := http.NewRequestWithContext(ctx, "POST", u.String(), http.NoBody)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req) resp, err := httpClient.Do(req)
if err != nil { if err != nil {
return nil, clientError(err) return nil, clientError(err)
} }
@ -753,7 +785,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var sign api.SignResponse var sign api.SignResponse
if err := readJSON(resp.Body, &sign); err != nil { if err := readJSON(resp.Body, &sign); err != nil {
@ -790,7 +822,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var sign api.SignResponse var sign api.SignResponse
if err := readJSON(resp.Body, &sign); err != nil { if err := readJSON(resp.Body, &sign); err != nil {
@ -814,14 +846,14 @@ func (c *Client) RekeyWithContext(ctx context.Context, req *api.RekeyRequest, tr
return nil, errors.Wrap(err, "error marshaling request") return nil, errors.Wrap(err, "error marshaling request")
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/rekey"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/rekey"})
client := &http.Client{Transport: tr} httpClient := &http.Client{Transport: tr}
retry: retry:
httpReq, err := http.NewRequestWithContext(ctx, "POST", u.String(), bytes.NewReader(body)) httpReq, err := http.NewRequestWithContext(ctx, "POST", u.String(), bytes.NewReader(body))
if err != nil { if err != nil {
return nil, err return nil, err
} }
httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Content-Type", "application/json")
resp, err := client.Do(httpReq) resp, err := httpClient.Do(httpReq)
if err != nil { if err != nil {
return nil, clientError(err) return nil, clientError(err)
} }
@ -830,7 +862,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var sign api.SignResponse var sign api.SignResponse
if err := readJSON(resp.Body, &sign); err != nil { if err := readJSON(resp.Body, &sign); err != nil {
@ -853,16 +885,16 @@ func (c *Client) RevokeWithContext(ctx context.Context, req *api.RevokeRequest,
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error marshaling request") return nil, errors.Wrap(err, "error marshaling request")
} }
var client *uaClient var uaClient *uaClient
retry: retry:
if tr != nil { if tr != nil {
client = newClient(tr) uaClient = newClient(tr)
} else { } else {
client = c.client uaClient = c.client
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/revoke"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/revoke"})
resp, err := client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body)) resp, err := uaClient.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return nil, clientError(err) return nil, clientError(err)
} }
@ -871,7 +903,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var revoke api.RevokeResponse var revoke api.RevokeResponse
if err := readJSON(resp.Body, &revoke); err != nil { if err := readJSON(resp.Body, &revoke); err != nil {
@ -914,7 +946,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var provisioners api.ProvisionersResponse var provisioners api.ProvisionersResponse
if err := readJSON(resp.Body, &provisioners); err != nil { if err := readJSON(resp.Body, &provisioners); err != nil {
@ -946,7 +978,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var key api.ProvisionerKeyResponse var key api.ProvisionerKeyResponse
if err := readJSON(resp.Body, &key); err != nil { if err := readJSON(resp.Body, &key); err != nil {
@ -976,7 +1008,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var roots api.RootsResponse var roots api.RootsResponse
if err := readJSON(resp.Body, &roots); err != nil { if err := readJSON(resp.Body, &roots); err != nil {
@ -1006,7 +1038,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var federation api.FederationResponse var federation api.FederationResponse
if err := readJSON(resp.Body, &federation); err != nil { if err := readJSON(resp.Body, &federation); err != nil {
@ -1040,7 +1072,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var sign api.SSHSignResponse var sign api.SSHSignResponse
if err := readJSON(resp.Body, &sign); err != nil { if err := readJSON(resp.Body, &sign); err != nil {
@ -1074,7 +1106,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var renew api.SSHRenewResponse var renew api.SSHRenewResponse
if err := readJSON(resp.Body, &renew); err != nil { if err := readJSON(resp.Body, &renew); err != nil {
@ -1108,7 +1140,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var rekey api.SSHRekeyResponse var rekey api.SSHRekeyResponse
if err := readJSON(resp.Body, &rekey); err != nil { if err := readJSON(resp.Body, &rekey); err != nil {
@ -1142,7 +1174,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var revoke api.SSHRevokeResponse var revoke api.SSHRevokeResponse
if err := readJSON(resp.Body, &revoke); err != nil { if err := readJSON(resp.Body, &revoke); err != nil {
@ -1172,7 +1204,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var keys api.SSHRootsResponse var keys api.SSHRootsResponse
if err := readJSON(resp.Body, &keys); err != nil { if err := readJSON(resp.Body, &keys); err != nil {
@ -1202,7 +1234,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var keys api.SSHRootsResponse var keys api.SSHRootsResponse
if err := readJSON(resp.Body, &keys); err != nil { if err := readJSON(resp.Body, &keys); err != nil {
@ -1236,7 +1268,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var cfg api.SSHConfigResponse var cfg api.SSHConfigResponse
if err := readJSON(resp.Body, &cfg); err != nil { if err := readJSON(resp.Body, &cfg); err != nil {
@ -1275,7 +1307,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var check api.SSHCheckPrincipalResponse var check api.SSHCheckPrincipalResponse
if err := readJSON(resp.Body, &check); err != nil { if err := readJSON(resp.Body, &check); err != nil {
@ -1304,7 +1336,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var hosts api.SSHGetHostsResponse var hosts api.SSHGetHostsResponse
if err := readJSON(resp.Body, &hosts); err != nil { if err := readJSON(resp.Body, &hosts); err != nil {
@ -1336,7 +1368,7 @@ retry:
retried = true retried = true
goto retry goto retry
} }
return nil, readError(resp.Body) return nil, readError(resp)
} }
var bastion api.SSHBastionResponse var bastion api.SSHBastionResponse
if err := readJSON(resp.Body, &bastion); err != nil { if err := readJSON(resp.Body, &bastion); err != nil {
@ -1504,12 +1536,13 @@ func readProtoJSON(r io.ReadCloser, m proto.Message) error {
return protojson.Unmarshal(data, m) return protojson.Unmarshal(data, m)
} }
func readError(r io.ReadCloser) error { func readError(r *http.Response) error {
defer r.Close() defer r.Body.Close()
apiErr := new(errs.Error) apiErr := new(errs.Error)
if err := json.NewDecoder(r).Decode(apiErr); err != nil { if err := json.NewDecoder(r.Body).Decode(apiErr); err != nil {
return err return fmt.Errorf("failed decoding CA error response: %w", err)
} }
apiErr.RequestID = r.Header.Get("X-Request-Id")
return apiErr return apiErr
} }

@ -0,0 +1,18 @@
package client
import "context"
type contextKey struct{}
// NewRequestIDContext returns a new context with the given request ID added to the
// context.
func NewRequestIDContext(ctx context.Context, requestID string) context.Context {
return context.WithValue(ctx, contextKey{}, requestID)
}
// RequestIDFromContext returns the request ID from the context if it exists.
// and is not empty.
func RequestIDFromContext(ctx context.Context) (string, bool) {
v, ok := ctx.Value(contextKey{}).(string)
return v, ok && v != ""
}

File diff suppressed because it is too large Load Diff

@ -7,6 +7,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
@ -41,14 +43,12 @@ func getTestProvisioner(t *testing.T, caURL string) *Provisioner {
} }
func TestNewProvisioner(t *testing.T) { func TestNewProvisioner(t *testing.T) {
ca := startCATestServer() ca := startCATestServer(t)
defer ca.Close() defer ca.Close()
want := getTestProvisioner(t, ca.URL) want := getTestProvisioner(t, ca.URL)
caBundle, err := os.ReadFile("testdata/secrets/root_ca.crt") caBundle, err := os.ReadFile("testdata/secrets/root_ca.crt")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
type args struct { type args struct {
name string name string

@ -69,7 +69,7 @@ func init() {
GetClientCertificate: id.GetClientCertificateFunc(), GetClientCertificate: id.GetClientCertificateFunc(),
}, },
} }
return func(ctx context.Context, network, address string) (net.Conn, error) { return func(ctx context.Context, _, _ string) (net.Conn, error) {
return d.DialContext(ctx, "tcp", net.JoinHostPort(host, port)) return d.DialContext(ctx, "tcp", net.JoinHostPort(host, port))
} }
} }

@ -10,6 +10,8 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
) )
@ -130,7 +132,7 @@ func TestVerifyClientCertIfGiven(t *testing.T) {
//nolint:gosec // test tls config //nolint:gosec // test tls config
func TestAddRootCA(t *testing.T) { func TestAddRootCA(t *testing.T) {
cert := parseCertificate(rootPEM) cert := parseCertificate(t, rootPEM)
pool := x509.NewCertPool() pool := x509.NewCertPool()
pool.AddCert(cert) pool.AddCert(cert)
@ -163,7 +165,7 @@ func TestAddRootCA(t *testing.T) {
//nolint:gosec // test tls config //nolint:gosec // test tls config
func TestAddClientCA(t *testing.T) { func TestAddClientCA(t *testing.T) {
cert := parseCertificate(rootPEM) cert := parseCertificate(t, rootPEM)
pool := x509.NewCertPool() pool := x509.NewCertPool()
pool.AddCert(cert) pool.AddCert(cert)
@ -196,25 +198,19 @@ func TestAddClientCA(t *testing.T) {
//nolint:gosec // test tls config //nolint:gosec // test tls config
func TestAddRootsToRootCAs(t *testing.T) { func TestAddRootsToRootCAs(t *testing.T) {
ca := startCATestServer() ca := startCATestServer(t)
defer ca.Close() defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt") root, err := os.ReadFile("testdata/secrets/root_ca.crt")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
cert := parseCertificate(string(root)) cert := parseCertificate(t, string(root))
pool := x509.NewCertPool() pool := x509.NewCertPool()
pool.AddCert(cert) pool.AddCert(cert)
@ -251,25 +247,19 @@ func TestAddRootsToRootCAs(t *testing.T) {
//nolint:gosec // test tls config //nolint:gosec // test tls config
func TestAddRootsToClientCAs(t *testing.T) { func TestAddRootsToClientCAs(t *testing.T) {
ca := startCATestServer() ca := startCATestServer(t)
defer ca.Close() defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt") root, err := os.ReadFile("testdata/secrets/root_ca.crt")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
cert := parseCertificate(string(root)) cert := parseCertificate(t, string(root))
pool := x509.NewCertPool() pool := x509.NewCertPool()
pool.AddCert(cert) pool.AddCert(cert)
@ -306,31 +296,23 @@ func TestAddRootsToClientCAs(t *testing.T) {
//nolint:gosec // test tls config //nolint:gosec // test tls config
func TestAddFederationToRootCAs(t *testing.T) { func TestAddFederationToRootCAs(t *testing.T) {
ca := startCATestServer() ca := startCATestServer(t)
defer ca.Close() defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt") root, err := os.ReadFile("testdata/secrets/root_ca.crt")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
federated, err := os.ReadFile("testdata/secrets/federated_ca.crt") federated, err := os.ReadFile("testdata/secrets/federated_ca.crt")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
crt1 := parseCertificate(string(root)) crt1 := parseCertificate(t, string(root))
crt2 := parseCertificate(string(federated)) crt2 := parseCertificate(t, string(federated))
pool := x509.NewCertPool() pool := x509.NewCertPool()
pool.AddCert(crt1) pool.AddCert(crt1)
pool.AddCert(crt2) pool.AddCert(crt2)
@ -371,31 +353,23 @@ func TestAddFederationToRootCAs(t *testing.T) {
//nolint:gosec // test tls config //nolint:gosec // test tls config
func TestAddFederationToClientCAs(t *testing.T) { func TestAddFederationToClientCAs(t *testing.T) {
ca := startCATestServer() ca := startCATestServer(t)
defer ca.Close() defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt") root, err := os.ReadFile("testdata/secrets/root_ca.crt")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
federated, err := os.ReadFile("testdata/secrets/federated_ca.crt") federated, err := os.ReadFile("testdata/secrets/federated_ca.crt")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
crt1 := parseCertificate(string(root)) crt1 := parseCertificate(t, string(root))
crt2 := parseCertificate(string(federated)) crt2 := parseCertificate(t, string(federated))
pool := x509.NewCertPool() pool := x509.NewCertPool()
pool.AddCert(crt1) pool.AddCert(crt1)
pool.AddCert(crt2) pool.AddCert(crt2)
@ -436,25 +410,19 @@ func TestAddFederationToClientCAs(t *testing.T) {
//nolint:gosec // test tls config //nolint:gosec // test tls config
func TestAddRootsToCAs(t *testing.T) { func TestAddRootsToCAs(t *testing.T) {
ca := startCATestServer() ca := startCATestServer(t)
defer ca.Close() defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt") root, err := os.ReadFile("testdata/secrets/root_ca.crt")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
cert := parseCertificate(string(root)) cert := parseCertificate(t, string(root))
pool := x509.NewCertPool() pool := x509.NewCertPool()
pool.AddCert(cert) pool.AddCert(cert)
@ -491,31 +459,23 @@ func TestAddRootsToCAs(t *testing.T) {
//nolint:gosec // test tls config //nolint:gosec // test tls config
func TestAddFederationToCAs(t *testing.T) { func TestAddFederationToCAs(t *testing.T) {
ca := startCATestServer() ca := startCATestServer(t)
defer ca.Close() defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt") root, err := os.ReadFile("testdata/secrets/root_ca.crt")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
federated, err := os.ReadFile("testdata/secrets/federated_ca.crt") federated, err := os.ReadFile("testdata/secrets/federated_ca.crt")
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
crt1 := parseCertificate(string(root)) crt1 := parseCertificate(t, string(root))
crt2 := parseCertificate(string(federated)) crt2 := parseCertificate(t, string(federated))
pool := x509.NewCertPool() pool := x509.NewCertPool()
pool.AddCert(crt1) pool.AddCert(crt1)
pool.AddCert(crt2) pool.AddCert(crt2)

@ -17,27 +17,28 @@ import (
"testing" "testing"
"time" "time"
"github.com/smallstep/certificates/api" "github.com/stretchr/testify/require"
"github.com/smallstep/certificates/authority"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/randutil" "go.step.sm/crypto/randutil"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority"
) )
func generateOTT(subject string) string { func generateOTT(t *testing.T, subject string) string {
t.Helper()
now := time.Now() now := time.Now()
jwk, err := jose.ReadKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password"))) jwk, err := jose.ReadKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password")))
if err != nil { require.NoError(t, err)
panic(err)
}
opts := new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID) opts := new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, opts) sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, opts)
if err != nil { require.NoError(t, err)
panic(err)
}
id, err := randutil.ASCII(64) id, err := randutil.ASCII(64)
if err != nil { require.NoError(t, err)
panic(err)
}
cl := struct { cl := struct {
jose.Claims jose.Claims
SANS []string `json:"sans"` SANS []string `json:"sans"`
@ -53,9 +54,8 @@ func generateOTT(subject string) string {
SANS: []string{subject}, SANS: []string{subject},
} }
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
if err != nil { require.NoError(t, err)
panic(err)
}
return raw return raw
} }
@ -72,32 +72,28 @@ func startTestServer(baseContext context.Context, tlsConfig *tls.Config, handler
return srv return srv
} }
func startCATestServer() *httptest.Server { func startCATestServer(t *testing.T) *httptest.Server {
config, err := authority.LoadConfiguration("testdata/ca.json") config, err := authority.LoadConfiguration("testdata/ca.json")
if err != nil { require.NoError(t, err)
panic(err)
}
ca, err := New(config) ca, err := New(config)
if err != nil { require.NoError(t, err)
panic(err)
}
// Use a httptest.Server instead // Use a httptest.Server instead
baseContext := buildContext(ca.auth, nil, nil, nil) baseContext := buildContext(ca.auth, nil, nil, nil)
srv := startTestServer(baseContext, ca.srv.TLSConfig, ca.srv.Handler) srv := startTestServer(baseContext, ca.srv.TLSConfig, ca.srv.Handler)
return srv return srv
} }
func sign(domain string) (*Client, *api.SignResponse, crypto.PrivateKey) { func sign(t *testing.T, domain string) (*Client, *api.SignResponse, crypto.PrivateKey) {
srv := startCATestServer() t.Helper()
srv := startCATestServer(t)
defer srv.Close() defer srv.Close()
return signDuration(srv, domain, 0) return signDuration(t, srv, domain, 0)
} }
func signDuration(srv *httptest.Server, domain string, duration time.Duration) (*Client, *api.SignResponse, crypto.PrivateKey) { func signDuration(t *testing.T, srv *httptest.Server, domain string, duration time.Duration) (*Client, *api.SignResponse, crypto.PrivateKey) {
req, pk, err := CreateSignRequest(generateOTT(domain)) t.Helper()
if err != nil { req, pk, err := CreateSignRequest(generateOTT(t, domain))
panic(err) require.NoError(t, err)
}
if duration > 0 { if duration > 0 {
req.NotBefore = api.NewTimeDuration(time.Now()) req.NotBefore = api.NewTimeDuration(time.Now())
@ -105,13 +101,11 @@ func signDuration(srv *httptest.Server, domain string, duration time.Duration) (
} }
client, err := NewClient(srv.URL, WithRootFile("testdata/secrets/root_ca.crt")) client, err := NewClient(srv.URL, WithRootFile("testdata/secrets/root_ca.crt"))
if err != nil { require.NoError(t, err)
panic(err)
}
sr, err := client.Sign(req) sr, err := client.Sign(req)
if err != nil { require.NoError(t, err)
panic(err)
}
return client, sr, pk return client, sr, pk
} }
@ -145,7 +139,7 @@ func serverHandler(t *testing.T, clientDomain string) http.Handler {
func TestClient_GetServerTLSConfig_http(t *testing.T) { func TestClient_GetServerTLSConfig_http(t *testing.T) {
clientDomain := "test.domain" clientDomain := "test.domain"
client, sr, pk := sign("127.0.0.1") client, sr, pk := sign(t, "127.0.0.1")
// Create mTLS server // Create mTLS server
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -212,7 +206,7 @@ func TestClient_GetServerTLSConfig_http(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
client, sr, pk := sign(clientDomain) client, sr, pk := sign(t, clientDomain)
cli := tt.getClient(t, client, sr, pk) cli := tt.getClient(t, client, sr, pk)
if cli == nil { if cli == nil {
return return
@ -246,19 +240,18 @@ func TestClient_GetServerTLSConfig_renew(t *testing.T) {
defer reset() defer reset()
// Start CA // Start CA
ca := startCATestServer() ca := startCATestServer(t)
defer ca.Close() defer ca.Close()
clientDomain := "test.domain" clientDomain := "test.domain"
client, sr, pk := signDuration(ca, "127.0.0.1", 5*time.Second) client, sr, pk := signDuration(t, ca, "127.0.0.1", 5*time.Second)
// Start mTLS server // Start mTLS server
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
tlsConfig, err := client.GetServerTLSConfig(ctx, sr, pk) tlsConfig, err := client.GetServerTLSConfig(ctx, sr, pk)
if err != nil { require.NoError(t, err)
t.Fatalf("Client.GetServerTLSConfig() error = %v", err)
}
srvMTLS := startTestServer(context.Background(), tlsConfig, serverHandler(t, clientDomain)) srvMTLS := startTestServer(context.Background(), tlsConfig, serverHandler(t, clientDomain))
defer srvMTLS.Close() defer srvMTLS.Close()
@ -266,30 +259,26 @@ func TestClient_GetServerTLSConfig_renew(t *testing.T) {
ctx, cancel = context.WithCancel(context.Background()) ctx, cancel = context.WithCancel(context.Background())
defer cancel() defer cancel()
tlsConfig, err = client.GetServerTLSConfig(ctx, sr, pk, VerifyClientCertIfGiven()) tlsConfig, err = client.GetServerTLSConfig(ctx, sr, pk, VerifyClientCertIfGiven())
if err != nil { require.NoError(t, err)
t.Fatalf("Client.GetServerTLSConfig() error = %v", err)
}
srvTLS := startTestServer(context.Background(), tlsConfig, serverHandler(t, clientDomain)) srvTLS := startTestServer(context.Background(), tlsConfig, serverHandler(t, clientDomain))
defer srvTLS.Close() defer srvTLS.Close()
// Transport // Transport
client, sr, pk = signDuration(ca, clientDomain, 5*time.Second) client, sr, pk = signDuration(t, ca, clientDomain, 5*time.Second)
tr1, err := client.Transport(context.Background(), sr, pk) tr1, err := client.Transport(context.Background(), sr, pk)
if err != nil { require.NoError(t, err)
t.Fatalf("Client.Transport() error = %v", err)
}
// Transport with tlsConfig // Transport with tlsConfig
client, sr, pk = signDuration(ca, clientDomain, 5*time.Second) client, sr, pk = signDuration(t, ca, clientDomain, 5*time.Second)
tlsConfig, err = client.GetClientTLSConfig(context.Background(), sr, pk) tlsConfig, err = client.GetClientTLSConfig(context.Background(), sr, pk)
if err != nil { require.NoError(t, err)
t.Fatalf("Client.GetClientTLSConfig() error = %v", err)
}
tr2 := getDefaultTransport(tlsConfig) tr2 := getDefaultTransport(tlsConfig)
// No client cert // No client cert
root, err := RootCertificate(sr) root, err := RootCertificate(sr)
if err != nil { require.NoError(t, err)
t.Fatalf("RootCertificate() error = %v", err)
}
tlsConfig = getDefaultTLSConfig(sr) tlsConfig = getDefaultTLSConfig(sr)
tlsConfig.RootCAs = x509.NewCertPool() tlsConfig.RootCAs = x509.NewCertPool()
tlsConfig.RootCAs.AddCert(root) tlsConfig.RootCAs.AddCert(root)
@ -401,13 +390,13 @@ func TestClient_GetServerTLSConfig_renew(t *testing.T) {
} }
func TestCertificate(t *testing.T) { func TestCertificate(t *testing.T) {
cert := parseCertificate(certPEM) cert := parseCertificate(t, certPEM)
ok := &api.SignResponse{ ok := &api.SignResponse{
ServerPEM: api.Certificate{Certificate: cert}, ServerPEM: api.Certificate{Certificate: cert},
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)},
CertChainPEM: []api.Certificate{ CertChainPEM: []api.Certificate{
{Certificate: cert}, {Certificate: cert},
{Certificate: parseCertificate(rootPEM)}, {Certificate: parseCertificate(t, rootPEM)},
}, },
} }
tests := []struct { tests := []struct {
@ -434,12 +423,12 @@ func TestCertificate(t *testing.T) {
} }
func TestIntermediateCertificate(t *testing.T) { func TestIntermediateCertificate(t *testing.T) {
intermediate := parseCertificate(rootPEM) intermediate := parseCertificate(t, rootPEM)
ok := &api.SignResponse{ ok := &api.SignResponse{
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)},
CaPEM: api.Certificate{Certificate: intermediate}, CaPEM: api.Certificate{Certificate: intermediate},
CertChainPEM: []api.Certificate{ CertChainPEM: []api.Certificate{
{Certificate: parseCertificate(certPEM)}, {Certificate: parseCertificate(t, certPEM)},
{Certificate: intermediate}, {Certificate: intermediate},
}, },
} }
@ -467,24 +456,24 @@ func TestIntermediateCertificate(t *testing.T) {
} }
func TestRootCertificateCertificate(t *testing.T) { func TestRootCertificateCertificate(t *testing.T) {
root := parseCertificate(rootPEM) root := parseCertificate(t, rootPEM)
ok := &api.SignResponse{ ok := &api.SignResponse{
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)},
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)},
CertChainPEM: []api.Certificate{ CertChainPEM: []api.Certificate{
{Certificate: parseCertificate(certPEM)}, {Certificate: parseCertificate(t, certPEM)},
{Certificate: parseCertificate(rootPEM)}, {Certificate: parseCertificate(t, rootPEM)},
}, },
TLS: &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{ TLS: &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{
{root, root}, {root, root},
}}, }},
} }
noTLS := &api.SignResponse{ noTLS := &api.SignResponse{
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)},
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)},
CertChainPEM: []api.Certificate{ CertChainPEM: []api.Certificate{
{Certificate: parseCertificate(certPEM)}, {Certificate: parseCertificate(t, certPEM)},
{Certificate: parseCertificate(rootPEM)}, {Certificate: parseCertificate(t, rootPEM)},
}, },
} }
tests := []struct { tests := []struct {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save