open-vault/builtin/logical/pkiext/zlint_test.go
2023-04-24 14:25:50 -04:00

193 lines
4.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package pkiext
import (
"context"
"encoding/json"
"sync"
"testing"
"github.com/hashicorp/vault/builtin/logical/pki"
"github.com/hashicorp/vault/sdk/helper/docker"
"github.com/stretchr/testify/require"
)
var (
zRunner *docker.Runner
buildZLintOnce sync.Once
)
func buildZLintContainer(t *testing.T) {
containerfile := `
FROM docker.mirror.hashicorp.services/library/golang:latest
RUN go install github.com/zmap/zlint/v3/cmd/zlint@latest
`
bCtx := docker.NewBuildContext()
imageName := "vault_pki_zlint_validator"
imageTag := "latest"
var err error
zRunner, err = docker.NewServiceRunner(docker.RunOptions{
ImageRepo: imageName,
ImageTag: imageTag,
ContainerName: "pki_zlint",
// We want to run sleep in the background so we're not stuck waiting
// for the default golang container's shell to prompt for input.
Entrypoint: []string{"sleep", "45"},
LogConsumer: func(s string) {
if t.Failed() {
t.Logf("container logs: %s", s)
}
},
})
if err != nil {
t.Fatalf("Could not provision docker service runner: %s", err)
}
ctx := context.Background()
output, err := zRunner.BuildImage(ctx, containerfile, bCtx,
docker.BuildRemove(true), docker.BuildForceRemove(true),
docker.BuildPullParent(true),
docker.BuildTags([]string{imageName + ":" + imageTag}))
if err != nil {
t.Fatalf("Could not build new image: %v", err)
}
t.Logf("Image build output: %v", string(output))
}
func RunZLintContainer(t *testing.T, certificate string) []byte {
buildZLintOnce.Do(func() {
buildZLintContainer(t)
})
ctx := context.Background()
// We don't actually care about the address, we just want to start the
// container so we can run commands in it. We'd ideally like to skip this
// step and only build a new image, but the zlint output would be
// intermingled with container build stages, so its not that useful.
result, err := zRunner.Start(ctx, true, false)
if err != nil {
t.Fatalf("Could not start golang container for zlint: %s", err)
}
// Copy the cert into the newly running container.
certCtx := docker.NewBuildContext()
certCtx["cert.pem"] = docker.PathContentsFromBytes([]byte(certificate))
if err := zRunner.CopyTo(result.Container.ID, "/go/", certCtx); err != nil {
t.Fatalf("Could not copy certificate into container: %v", err)
}
// Run the zlint command and save the output.
cmd := []string{"/go/bin/zlint", "/go/cert.pem"}
stdout, stderr, retcode, err := zRunner.RunCmdWithOutput(ctx, result.Container.ID, cmd)
if err != nil {
t.Fatalf("Could not run command in container: %v", err)
}
if len(stderr) != 0 {
t.Logf("Got stderr from command:\n%v\n", string(stderr))
}
if retcode != 0 {
t.Logf("Got stdout from command:\n%v\n", string(stdout))
t.Fatalf("Got unexpected non-zero retcode from zlint: %v\n", retcode)
}
// Clean up after ourselves.
if err := zRunner.Stop(context.Background(), result.Container.ID); err != nil {
t.Fatalf("failed to stop container: %v", err)
}
return stdout
}
func RunZLintRootTest(t *testing.T, keyType string, keyBits int, usePSS bool, ignored []string) {
b, s := pki.CreateBackendWithStorage(t)
resp, err := pki.CBWrite(b, s, "root/generate/internal", map[string]interface{}{
"common_name": "Root X1",
"country": "US",
"organization": "Dadgarcorp",
"ou": "QA",
"key_type": keyType,
"key_bits": keyBits,
"use_pss": usePSS,
})
require.NoError(t, err)
rootCert := resp.Data["certificate"].(string)
var parsed map[string]interface{}
output := RunZLintContainer(t, rootCert)
if err := json.Unmarshal(output, &parsed); err != nil {
t.Fatalf("failed to parse zlint output as JSON: %v\nOutput:\n%v\n\n", err, string(output))
}
for key, rawValue := range parsed {
value := rawValue.(map[string]interface{})
result, ok := value["result"]
if !ok || result == "NA" {
continue
}
if result == "error" {
skip := false
for _, allowedFailures := range ignored {
if allowedFailures == key {
skip = true
break
}
}
if !skip {
t.Fatalf("got unexpected error from test %v: %v", key, value)
}
}
}
}
func Test_ZLintRSA2048(t *testing.T) {
t.Parallel()
RunZLintRootTest(t, "rsa", 2048, false, nil)
}
func Test_ZLintRSA2048PSS(t *testing.T) {
t.Parallel()
RunZLintRootTest(t, "rsa", 2048, true, nil)
}
func Test_ZLintRSA3072(t *testing.T) {
t.Parallel()
RunZLintRootTest(t, "rsa", 3072, false, nil)
}
func Test_ZLintRSA3072PSS(t *testing.T) {
t.Parallel()
RunZLintRootTest(t, "rsa", 3072, true, nil)
}
func Test_ZLintECDSA256(t *testing.T) {
t.Parallel()
RunZLintRootTest(t, "ec", 256, false, nil)
}
func Test_ZLintECDSA384(t *testing.T) {
t.Parallel()
RunZLintRootTest(t, "ec", 384, false, nil)
}
func Test_ZLintECDSA521(t *testing.T) {
t.Parallel()
// Mozilla doesn't allow P-521 ECDSA keys.
RunZLintRootTest(t, "ec", 521, false, []string{
"e_mp_ecdsa_pub_key_encoding_correct",
"e_mp_ecdsa_signature_encoding_correct",
})
}