pomerium/internal/autocert/manager_test.go
dependabot[bot] 3fcce1d9ef
chore(deps): bump the go group with 27 updates (#5122)
* chore(deps): bump the go group with 27 updates

Bumps the go group with 27 updates:

| Package | From | To |
| --- | --- | --- |
| [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) | `1.40.0` | `1.41.0` |
| [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) | `1.26.1` | `1.27.0` |
| [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) | `1.27.11` | `1.27.16` |
| [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) | `1.53.1` | `1.54.3` |
| [github.com/caddyserver/certmagic](https://github.com/caddyserver/certmagic) | `0.20.0` | `0.21.2` |
| [github.com/docker/docker](https://github.com/docker/docker) | `26.1.1+incompatible` | `26.1.3+incompatible` |
| [github.com/jackc/pgx/v5](https://github.com/jackc/pgx) | `5.5.5` | `5.6.0` |
| [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) | `0.64.1` | `0.65.0` |
| [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) | `1.19.0` | `1.19.1` |
| [github.com/prometheus/procfs](https://github.com/prometheus/procfs) | `0.14.0` | `0.15.1` |
| [github.com/rs/zerolog](https://github.com/rs/zerolog) | `1.32.0` | `1.33.0` |
| [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) | `3.24.4` | `3.24.5` |
| [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) | `1.26.0` | `1.27.0` |
| [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc](https://github.com/open-telemetry/opentelemetry-go) | `1.26.0` | `1.27.0` |
| [go.opentelemetry.io/otel/exporters/otlp/otlptrace](https://github.com/open-telemetry/opentelemetry-go) | `1.26.0` | `1.27.0` |
| [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) | `1.26.0` | `1.27.0` |
| [go.opentelemetry.io/otel/metric](https://github.com/open-telemetry/opentelemetry-go) | `1.26.0` | `1.27.0` |
| [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) | `1.26.0` | `1.27.0` |
| [go.opentelemetry.io/otel/sdk/metric](https://github.com/open-telemetry/opentelemetry-go) | `1.26.0` | `1.27.0` |
| [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) | `1.26.0` | `1.27.0` |
| [golang.org/x/crypto](https://github.com/golang/crypto) | `0.22.0` | `0.23.0` |
| [golang.org/x/net](https://github.com/golang/net) | `0.24.0` | `0.25.0` |
| [golang.org/x/oauth2](https://github.com/golang/oauth2) | `0.19.0` | `0.20.0` |
| [google.golang.org/api](https://github.com/googleapis/google-api-go-client) | `0.177.0` | `0.178.0` |
| [google.golang.org/genproto/googleapis/rpc](https://github.com/googleapis/go-genproto) | `0.0.0-20240429193739-8cf5692501f6` | `0.0.0-20240515191416-fc5f0ca64291` |
| [google.golang.org/grpc](https://github.com/grpc/grpc-go) | `1.63.2` | `1.64.0` |
| google.golang.org/protobuf | `1.34.0` | `1.34.1` |


Updates `cloud.google.com/go/storage` from 1.40.0 to 1.41.0
- [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/spanner/v1.40.0...spanner/v1.41.0)

Updates `github.com/aws/aws-sdk-go-v2` from 1.26.1 to 1.27.0
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.26.1...v1.27.0)

Updates `github.com/aws/aws-sdk-go-v2/config` from 1.27.11 to 1.27.16
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.11...config/v1.27.16)

Updates `github.com/aws/aws-sdk-go-v2/service/s3` from 1.53.1 to 1.54.3
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.53.1...service/s3/v1.54.3)

Updates `github.com/caddyserver/certmagic` from 0.20.0 to 0.21.2
- [Release notes](https://github.com/caddyserver/certmagic/releases)
- [Commits](https://github.com/caddyserver/certmagic/compare/v0.20.0...v0.21.2)

Updates `github.com/docker/docker` from 26.1.1+incompatible to 26.1.3+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.1.1...v26.1.3)

Updates `github.com/jackc/pgx/v5` from 5.5.5 to 5.6.0
- [Changelog](https://github.com/jackc/pgx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jackc/pgx/compare/v5.5.5...v5.6.0)

Updates `github.com/open-policy-agent/opa` from 0.64.1 to 0.65.0
- [Release notes](https://github.com/open-policy-agent/opa/releases)
- [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-policy-agent/opa/compare/v0.64.1...v0.65.0)

Updates `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)

Updates `github.com/prometheus/procfs` from 0.14.0 to 0.15.1
- [Release notes](https://github.com/prometheus/procfs/releases)
- [Commits](https://github.com/prometheus/procfs/compare/v0.14.0...v0.15.1)

Updates `github.com/rs/zerolog` from 1.32.0 to 1.33.0
- [Commits](https://github.com/rs/zerolog/compare/v1.32.0...v1.33.0)

Updates `github.com/shirou/gopsutil/v3` from 3.24.4 to 3.24.5
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.24.4...v3.24.5)

Updates `go.opentelemetry.io/otel` from 1.26.0 to 1.27.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.26.0...v1.27.0)

Updates `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` from 1.26.0 to 1.27.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.26.0...v1.27.0)

Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace` from 1.26.0 to 1.27.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.26.0...v1.27.0)

Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc` from 1.26.0 to 1.27.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.26.0...v1.27.0)

Updates `go.opentelemetry.io/otel/metric` from 1.26.0 to 1.27.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.26.0...v1.27.0)

Updates `go.opentelemetry.io/otel/sdk` from 1.26.0 to 1.27.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.26.0...v1.27.0)

Updates `go.opentelemetry.io/otel/sdk/metric` from 1.26.0 to 1.27.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.26.0...v1.27.0)

Updates `go.opentelemetry.io/otel/trace` from 1.26.0 to 1.27.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.26.0...v1.27.0)

Updates `golang.org/x/crypto` from 0.22.0 to 0.23.0
- [Commits](https://github.com/golang/crypto/compare/v0.22.0...v0.23.0)

Updates `golang.org/x/net` from 0.24.0 to 0.25.0
- [Commits](https://github.com/golang/net/compare/v0.24.0...v0.25.0)

Updates `golang.org/x/oauth2` from 0.19.0 to 0.20.0
- [Commits](https://github.com/golang/oauth2/compare/v0.19.0...v0.20.0)

Updates `google.golang.org/api` from 0.177.0 to 0.178.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.178.0)

Updates `google.golang.org/genproto/googleapis/rpc` from 0.0.0-20240429193739-8cf5692501f6 to 0.0.0-20240515191416-fc5f0ca64291
- [Commits](https://github.com/googleapis/go-genproto/commits)

Updates `google.golang.org/grpc` 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)

Updates `google.golang.org/protobuf` from 1.34.0 to 1.34.1

---
updated-dependencies:
- dependency-name: cloud.google.com/go/storage
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/aws/aws-sdk-go-v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/caddyserver/certmagic
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/jackc/pgx/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/open-policy-agent/opa
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/prometheus/procfs
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/rs/zerolog
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/metric
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/sdk/metric
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: google.golang.org/genproto/googleapis/rpc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
...

Signed-off-by: dependabot[bot] <support@github.com>

* change acme pkg

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Denis Mishin <dmishin@pomerium.com>
2024-06-03 14:35:29 -04:00

632 lines
17 KiB
Go

package autocert
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
"github.com/caddyserver/certmagic"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/mholt/acmez/v2/acme"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ocsp"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
)
type M = map[string]any
type testCA struct {
key *ecdsa.PrivateKey
cert *x509.Certificate
certPEM []byte
}
func newTestCA() (*testCA, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
tpl := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
CommonName: "Test CA",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Minute * 10),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
BasicConstraintsValid: true,
IsCA: true,
}
der, err := x509.CreateCertificate(rand.Reader, tpl, tpl, &key.PublicKey, key)
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(der)
if err != nil {
return nil, err
}
return &testCA{
key,
cert,
pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}),
}, nil
}
func newMockACME(ca *testCA, srv *httptest.Server) http.Handler {
var certBuffer bytes.Buffer
var certs []*x509.Certificate
findCert := func(serial *big.Int) *x509.Certificate {
for _, c := range certs {
if c.SerialNumber.Cmp(serial) == 0 {
return c
}
}
return nil
}
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/acme/directory", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(M{
"keyChange": srv.URL + "/acme/key-change",
"newAccount": srv.URL + "/acme/new-acct",
"newNonce": srv.URL + "/acme/new-nonce",
"newOrder": srv.URL + "/acme/new-order",
"revokeCert": srv.URL + "/acme/revoke-cert",
})
})
r.Head("/acme/new-nonce", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Replay-Nonce", "NONCE")
w.WriteHeader(http.StatusOK)
})
r.Post("/acme/new-acct", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Replay-Nonce", "NONCE")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(M{
"status": "valid",
})
})
r.Post("/acme/new-order", func(w http.ResponseWriter, r *http.Request) {
var payload struct {
Identifiers []struct {
Type string `json:"type"`
Value string `json:"value"`
} `json:"identifiers"`
}
readJWSPayload(r.Body, &payload)
w.Header().Set("Replay-Nonce", "NONCE")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(M{
"status": "pending",
"finalize": srv.URL + "/acme/finalize",
})
})
r.Post("/ocsp/request", func(w http.ResponseWriter, r *http.Request) {
reqData, _ := io.ReadAll(r.Body)
ocspReq, _ := ocsp.ParseRequest(reqData)
ocspResp := ocsp.Response{
Status: ocsp.Good,
SerialNumber: ocspReq.SerialNumber,
ThisUpdate: time.Now(),
NextUpdate: time.Now().Add(time.Second),
}
cert := findCert(ocspReq.SerialNumber)
data, _ := ocsp.CreateResponse(ca.cert, cert, ocspResp, ca.key)
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
})
r.Post("/acme/finalize", func(w http.ResponseWriter, r *http.Request) {
var payload struct {
CSR string `json:"csr"`
}
readJWSPayload(r.Body, &payload)
bs, _ := base64.RawURLEncoding.DecodeString(payload.CSR)
csr, _ := x509.ParseCertificateRequest(bs)
tpl := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().Unix()),
DNSNames: csr.DNSNames,
IPAddresses: csr.IPAddresses,
Subject: pkix.Name{
CommonName: csr.DNSNames[0],
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Second * 2),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
IsCA: false,
IssuingCertificateURL: []string{srv.URL + "/certs/ca"},
OCSPServer: []string{srv.URL + "/ocsp/request"},
}
der, _ := x509.CreateCertificate(rand.Reader, tpl, ca.cert, csr.PublicKey, ca.key)
certBuffer.Reset()
_ = pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: der})
cert, _ := x509.ParseCertificate(der)
certs = append(certs, cert)
w.Header().Set("Replay-Nonce", "NONCE")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(M{
"status": "valid",
"finalize": srv.URL + "/acme/finalize",
"certificate": srv.URL + "/acme/certificate",
})
})
r.Post("/acme/certificate", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Replay-Nonce", "NONCE")
w.Header().Set("Content-Type", "application/pem-certificate-chain")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(certBuffer.Bytes())
})
r.Get("/certs/ca", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/pkix-cert")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(ca.cert.Raw)
})
return r
}
func TestConfig(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var mockACME http.Handler
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mockACME.ServeHTTP(w, r)
}))
defer srv.Close()
ca, err := newTestCA()
require.NoError(t, err)
mockACME = newMockACME(ca, srv)
// avoid using t.TempDir so tests don't fail: https://github.com/pomerium/pomerium/issues/4757
tmpdir := filepath.Join(os.TempDir(), uuid.New().String())
_ = os.MkdirAll(tmpdir, 0o755)
defer os.RemoveAll(tmpdir)
li, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
addr := li.Addr().String()
_ = li.Close()
to, err := config.ParseWeightedUrls("http://to.example.com")
require.NoError(t, err)
p1 := config.Policy{
From: "http://from.example.com", To: to,
}
_ = p1.Validate()
mgr, err := newManager(ctx, config.NewStaticSource(&config.Config{
Options: &config.Options{
AutocertOptions: config.AutocertOptions{
Enable: true,
UseStaging: true,
Email: "pomerium-test@example.com",
MustStaple: true,
Folder: tmpdir,
},
HTTPRedirectAddr: addr,
Policies: []config.Policy{p1},
},
}), certmagic.ACMEIssuer{
CA: srv.URL + "/acme/directory",
TestCA: srv.URL + "/acme/directory",
}, time.Millisecond*100)
if !assert.NoError(t, err) {
return
}
domainRenewed := make(chan bool)
ocspUpdated := make(chan bool)
var initialOCSPStaple []byte
var certValidTime *time.Time
mgr.OnConfigChange(ctx, func(ctx context.Context, cfg *config.Config) {
if len(cfg.AutoCertificates) == 0 {
return
}
cert := cfg.AutoCertificates[0]
if initialOCSPStaple == nil {
initialOCSPStaple = cert.OCSPStaple
} else {
if !bytes.Equal(initialOCSPStaple, cert.OCSPStaple) {
log.Info(ctx).Msg("OCSP updated")
ocspUpdated <- true
}
}
if certValidTime == nil {
certValidTime = &cert.Leaf.NotAfter
} else {
if !certValidTime.Equal(cert.Leaf.NotAfter) {
log.Info(ctx).Msg("domain renewed")
domainRenewed <- true
}
}
})
domainRenewedOK := false
ocspUpdatedOK := false
for !domainRenewedOK || !ocspUpdatedOK {
select {
case <-time.After(time.Second * 10):
t.Error("timeout waiting for certs renewal")
return
case domainRenewedOK = <-domainRenewed:
case ocspUpdatedOK = <-ocspUpdated:
}
}
}
func TestRedirect(t *testing.T) {
li, err := net.Listen("tcp", "127.0.0.1:0")
if !assert.NoError(t, err) {
return
}
addr := li.Addr().String()
_ = li.Close()
src := config.NewStaticSource(&config.Config{
Options: &config.Options{
HTTPRedirectAddr: addr,
SetResponseHeaders: map[string]string{
"X-Frame-Options": "SAMEORIGIN",
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
},
},
})
_, err = New(src)
if !assert.NoError(t, err) {
return
}
err = waitFor(addr)
if !assert.NoError(t, err) {
return
}
client := &http.Client{
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
return http.ErrUseLastResponse
},
}
res, err := client.Get(fmt.Sprintf("http://%s", addr))
if !assert.NoError(t, err) {
return
}
defer res.Body.Close()
assert.Equal(t, http.StatusMovedPermanently, res.StatusCode, "should redirect to https")
for k, v := range src.GetConfig().Options.SetResponseHeaders {
assert.NotEqual(t, v, res.Header.Get(k), "should ignore options header")
}
}
func waitFor(addr string) error {
var err error
deadline := time.Now().Add(time.Second * 30)
for time.Now().Before(deadline) {
var conn net.Conn
conn, err = net.Dial("tcp", addr)
if err == nil {
conn.Close()
return nil
}
time.Sleep(time.Second)
}
return err
}
func readJWSPayload(r io.Reader, dst any) {
var req struct {
Protected string `json:"protected"`
Payload string `json:"payload"`
Signature string `json:"signature"`
}
_ = json.NewDecoder(r).Decode(&req)
bs, _ := base64.RawURLEncoding.DecodeString(req.Payload)
_ = json.Unmarshal(bs, dst)
}
func newACMEIssuer() *certmagic.ACMEIssuer {
return &certmagic.ACMEIssuer{
CA: certmagic.DefaultACME.CA,
TestCA: certmagic.DefaultACME.TestCA,
}
}
func Test_configureCertificateAuthority(t *testing.T) {
type args struct {
acmeMgr *certmagic.ACMEIssuer
opts config.AutocertOptions
}
type test struct {
args args
expected *certmagic.ACMEIssuer
wantErr bool
}
tests := map[string]func(t *testing.T) test{
"ok/default": func(_ *testing.T) test {
return test{
args: args{
acmeMgr: newACMEIssuer(),
opts: config.AutocertOptions{},
},
expected: &certmagic.ACMEIssuer{
Agreed: true,
CA: certmagic.DefaultACME.CA,
Email: " ",
TestCA: certmagic.DefaultACME.TestCA,
},
wantErr: false,
}
},
"ok/staging": func(_ *testing.T) test {
return test{
args: args{
acmeMgr: newACMEIssuer(),
opts: config.AutocertOptions{
UseStaging: true,
},
},
expected: &certmagic.ACMEIssuer{
Agreed: true,
CA: certmagic.DefaultACME.TestCA,
Email: " ",
TestCA: certmagic.DefaultACME.TestCA,
},
wantErr: false,
}
},
"ok/custom-ca-staging": func(_ *testing.T) test {
return test{
args: args{
acmeMgr: newACMEIssuer(),
opts: config.AutocertOptions{
CA: "test-ca.example.com/directory",
Email: "test@example.com",
UseStaging: true,
},
},
expected: &certmagic.ACMEIssuer{
Agreed: true,
CA: "test-ca.example.com/directory",
Email: "test@example.com",
TestCA: certmagic.DefaultACME.TestCA,
},
wantErr: false,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
if err := configureCertificateAuthority(tc.args.acmeMgr, tc.args.opts); (err != nil) != tc.wantErr {
t.Errorf("configureCertificateAuthority() error = %v, wantErr %v", err, tc.wantErr)
}
if !cmp.Equal(tc.expected, tc.args.acmeMgr, cmpopts.IgnoreUnexported(certmagic.ACMEIssuer{})) {
t.Errorf("configureCertificateAuthority() diff = %s", cmp.Diff(tc.expected, tc.args.acmeMgr, cmpopts.IgnoreUnexported(certmagic.ACMEIssuer{})))
}
})
}
}
func Test_configureExternalAccountBinding(t *testing.T) {
type args struct {
acmeMgr *certmagic.ACMEIssuer
opts config.AutocertOptions
}
type test struct {
args args
expected *certmagic.ACMEIssuer
wantErr bool
}
tests := map[string]func(t *testing.T) test{
"ok": func(_ *testing.T) test {
return test{
args: args{
acmeMgr: newACMEIssuer(),
opts: config.AutocertOptions{
EABKeyID: "keyID",
EABMACKey: "29D7t6-mOuEV5vvBRX0UYF5T7x6fomidhM1kMJco-yw",
},
},
expected: &certmagic.ACMEIssuer{
CA: certmagic.DefaultACME.CA,
TestCA: certmagic.DefaultACME.TestCA,
ExternalAccount: &acme.EAB{
KeyID: "keyID",
MACKey: "29D7t6-mOuEV5vvBRX0UYF5T7x6fomidhM1kMJco-yw",
},
},
wantErr: false,
}
},
"fail/error-decoding-mac-key": func(_ *testing.T) test {
return test{
args: args{
acmeMgr: newACMEIssuer(),
opts: config.AutocertOptions{
EABKeyID: "keyID",
EABMACKey: ">invalid-base-64-data<",
},
},
wantErr: true,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
err := configureExternalAccountBinding(tc.args.acmeMgr, tc.args.opts)
if (err != nil) != tc.wantErr {
t.Errorf("configureExternalAccountBinding() error = %v, wantErr %v", err, tc.wantErr)
}
if err == nil && !cmp.Equal(tc.expected, tc.args.acmeMgr, cmpopts.IgnoreUnexported(certmagic.ACMEIssuer{})) {
t.Errorf("configureCertificateAuthority() diff = %s", cmp.Diff(tc.expected, tc.args.acmeMgr, cmpopts.IgnoreUnexported(certmagic.ACMEIssuer{})))
}
})
}
}
func Test_configureTrustedRoots(t *testing.T) {
ca, err := newTestCA()
require.NoError(t, err)
type args struct {
acmeMgr *certmagic.ACMEIssuer
opts config.AutocertOptions
}
type test struct {
args args
expected *certmagic.ACMEIssuer
wantErr bool
cleanup func()
}
tests := map[string]func(t *testing.T) test{
"ok/pem": func(t *testing.T) test {
roots, err := x509.SystemCertPool()
require.NoError(t, err)
ok := roots.AppendCertsFromPEM(ca.certPEM)
require.Equal(t, true, ok)
return test{
args: args{
acmeMgr: newACMEIssuer(),
opts: config.AutocertOptions{
TrustedCA: base64.StdEncoding.EncodeToString(ca.certPEM),
},
},
expected: &certmagic.ACMEIssuer{
CA: certmagic.DefaultACME.CA,
TestCA: certmagic.DefaultACME.TestCA,
TrustedRoots: roots,
},
wantErr: false,
}
},
"ok/file": func(t *testing.T) test {
roots, err := x509.SystemCertPool()
require.NoError(t, err)
ok := roots.AppendCertsFromPEM(ca.certPEM)
require.Equal(t, true, ok)
f, err := os.CreateTemp("", "pomerium-test-ca")
require.NoError(t, err)
n, err := f.Write(ca.certPEM)
require.NoError(t, err)
require.Equal(t, len(ca.certPEM), n)
return test{
args: args{
acmeMgr: newACMEIssuer(),
opts: config.AutocertOptions{
TrustedCAFile: f.Name(),
},
},
expected: &certmagic.ACMEIssuer{
CA: certmagic.DefaultACME.CA,
TestCA: certmagic.DefaultACME.TestCA,
TrustedRoots: roots,
},
wantErr: false,
cleanup: func() {
os.Remove(f.Name())
},
}
},
"fail/pem": func(t *testing.T) test {
roots, err := x509.SystemCertPool()
require.NoError(t, err)
return test{
args: args{
acmeMgr: newACMEIssuer(),
opts: config.AutocertOptions{
TrustedCA: ">invalid-base-64-ca-pem<",
},
},
expected: &certmagic.ACMEIssuer{
CA: certmagic.DefaultACME.CA,
TestCA: certmagic.DefaultACME.TestCA,
TrustedRoots: roots,
},
wantErr: true,
}
},
"fail/file": func(t *testing.T) test {
roots, err := x509.SystemCertPool()
require.NoError(t, err)
return test{
args: args{
acmeMgr: newACMEIssuer(),
opts: config.AutocertOptions{
TrustedCAFile: "some-non-existing-file",
},
},
expected: &certmagic.ACMEIssuer{
CA: certmagic.DefaultACME.CA,
TestCA: certmagic.DefaultACME.TestCA,
TrustedRoots: roots,
},
wantErr: true,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
err := configureTrustedRoots(tc.args.acmeMgr, tc.args.opts)
if (err != nil) != tc.wantErr {
t.Errorf("configureTrustedRoots() error = %v, wantErr %v", err, tc.wantErr)
}
if err == nil && !cmp.Equal(tc.expected, tc.args.acmeMgr, cmpopts.IgnoreUnexported(certmagic.ACMEIssuer{}, x509.CertPool{})) {
t.Errorf("configureCertificateAuthority() diff = %s", cmp.Diff(tc.expected, tc.args.acmeMgr, cmpopts.IgnoreUnexported(certmagic.ACMEIssuer{}, x509.CertPool{})))
}
if err == nil && !cmp.Equal(tc.expected.TrustedRoots.Subjects(), tc.args.acmeMgr.TrustedRoots.Subjects()) {
t.Errorf("configureCertificateAuthority() subjects diff = %s", cmp.Diff(tc.expected.TrustedRoots.Subjects(), tc.args.acmeMgr.TrustedRoots.Subjects()))
}
if tc.cleanup != nil {
tc.cleanup()
}
})
}
}