forked from TrueCloudLab/certificates
Merge branch 'master' into ssh-config
This commit is contained in:
commit
9cfd84324e
54 changed files with 2384 additions and 940 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -18,8 +18,3 @@
|
|||
coverage.txt
|
||||
vendor
|
||||
output
|
||||
|
||||
# Ignore modules until switch from gopkg
|
||||
go.mod
|
||||
go.sum
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ linters:
|
|||
- deadcode
|
||||
- staticcheck
|
||||
- unused
|
||||
- gosimple
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
|
|
479
Gopkg.lock
generated
479
Gopkg.lock
generated
|
@ -1,479 +0,0 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:6716c9fe6333591128e72848f246fc01dc72240e1e64185d8b4e124e7280b35d"
|
||||
name = "github.com/AndreasBriese/bbloom"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "e2d15f34fcf99d5dbb871c820ec73f710fca9815"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3b10c6fd33854dc41de2cf78b7bae105da94c2789b6fa5b9ac9e593ea43484ac"
|
||||
name = "github.com/Masterminds/goutils"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "41ac8693c5c10a92ea1ff5ac3a7f95646f6123b0"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:181a6a5506bd83827d826df5b3272040e92c487516b2fc8edd066be941d68d9e"
|
||||
name = "github.com/Masterminds/sprig"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0e09f04f09aede2cc36cbecad7ec27b0055303e0"
|
||||
version = "v3.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:454adc7f974228ff789428b6dc098638c57a64aa0718f0bd61e53d3cd39d7a75"
|
||||
name = "github.com/chzyer/readline"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2972be24d48e78746da79ba8e24e8b488c9880de"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:21ac9938fb1098b3a7b0dd909fb30878d33231177fac11a2821114eb9c1088ff"
|
||||
name = "github.com/dgraph-io/badger"
|
||||
packages = [
|
||||
".",
|
||||
"options",
|
||||
"protos",
|
||||
"skl",
|
||||
"table",
|
||||
"y",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "391b6d3b93e6014fe8c2971fcc0c1266e47dbbd9"
|
||||
version = "v1.5.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:6e8109ce247a59ab1eeb5330166c12735f6590de99c9647b6162d11518d32c9a"
|
||||
name = "github.com/dgryski/go-farm"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "6a90982ecee230ff6cba02d5bd386acc030be9d3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:81fda4d18a16651bf92245ce5d6178cdd99f918db30ae9794732655f0686e895"
|
||||
name = "github.com/go-chi/chi"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0ebf7795c516423a110473652e9ba3a59a504863"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65"
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "72cd26f257d44c1114970e19afddcd812016007e"
|
||||
version = "v1.4.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
pruneopts = "UT"
|
||||
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:582b704bebaa06b48c29b0cec224a6058a09c86883aaddabde889cd1a5f73e1b"
|
||||
name = "github.com/google/uuid"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0cd6bf5da1e1c83f8b45653022c74f71af0538a4"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f9a5e090336881be43cfc1cf468330c1bdd60abdc9dd194e0b1ab69f4b94dd7c"
|
||||
name = "github.com/huandu/xstrings"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f02667b379e2fb5916c3cda2cf31e0eb885d79f8"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:78d28d5b84a26159c67ea51996a230da4bc07cac648adaae1dfb5fc0ec8e40d3"
|
||||
name = "github.com/imdario/mergo"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "1afb36080aec31e0d1528973ebe6721b191b0369"
|
||||
version = "v0.3.8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e51f40f0c19b39c1825eadd07d5c0a98a2ad5942b166d9fc4f54750ce9a04810"
|
||||
name = "github.com/juju/ansiterm"
|
||||
packages = [
|
||||
".",
|
||||
"tabwriter",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "720a0952cc2ac777afc295d9861263e2a4cf96a1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0a69a1c0db3591fcefb47f115b224592c8dfa4368b7ba9fae509d5e16cdc95c8"
|
||||
name = "github.com/konsorten/go-windows-terminal-sequences"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:bb08c7bb1c7224636b1a00639f079ed4391eb822945f26db74b8d8ee3f14d991"
|
||||
name = "github.com/lunixbochs/vtclean"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2d01aacdc34a083dca635ba869909f5fc0cd4f41"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2d2bc0f23cca6b59cec3fbece9abc102bdb19f548dd58d7667e57699074a2c76"
|
||||
name = "github.com/manifoldco/promptui"
|
||||
packages = [
|
||||
".",
|
||||
"list",
|
||||
"screenbuf",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "157c96fb638a14d268b305cf2012582431fcc410"
|
||||
version = "v0.3.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0981502f9816113c9c8c4ac301583841855c8cf4da8c72f696b3ebedf6d0e4e5"
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c"
|
||||
version = "v0.0.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:09ca328575f38b80969ccf857f6d7302f2ce09d53778ea7aaba526cfd2cec739"
|
||||
name = "github.com/mitchellh/copystructure"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9a1b6f44e8da0e0e374624fb0a825a231b00c537"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2a7e6f8bebdca6bd8bc359c37f01ae1c4ea4f8481eaabf93b1ae4863f15b72c7"
|
||||
name = "github.com/mitchellh/reflectwalk"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "3e2c75dfad4fbf904b58782a80fd595c760ad185"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ae08d850ba158ea3ba4a7bb90f8372608172d8920644e5a6693b940a1f4e5d01"
|
||||
name = "github.com/mmcloughlin/avo"
|
||||
packages = [
|
||||
"attr",
|
||||
"build",
|
||||
"buildtags",
|
||||
"gotypes",
|
||||
"internal/prnt",
|
||||
"internal/stack",
|
||||
"ir",
|
||||
"operand",
|
||||
"pass",
|
||||
"printer",
|
||||
"reg",
|
||||
"src",
|
||||
"x86",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "2e7d06bc7ada2979f17ccf8ebf486dba23b84fc7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:266d082179f3a29a4bdcf1dcc49d4a304f5c7107e65bd22d1fecacf45f1ac348"
|
||||
name = "github.com/newrelic/go-agent"
|
||||
packages = [
|
||||
".",
|
||||
"internal",
|
||||
"internal/cat",
|
||||
"internal/jsonx",
|
||||
"internal/logger",
|
||||
"internal/sysinfo",
|
||||
"internal/utilization",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f5bce3387232559bcbe6a5f8227c4bf508dac1ba"
|
||||
version = "v1.11.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
|
||||
version = "v0.8.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2e76a73cb51f42d63a2a1a85b3dc5731fd4faf6821b434bd0ef2c099186031d6"
|
||||
name = "github.com/rs/xid"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "15d26544def341f036c5f8dca987a4cbe575032c"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8baa3b16f20963c54e296627ea1dabfd79d1b486f81baf8759e99d73bddf2687"
|
||||
name = "github.com/samfoo/ansi"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "b6bd2ded7189ce35bc02233b554eb56a5146af73"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:def689e73e9252f6f7fe66834a76751a41b767e03daab299e607e7226c58a855"
|
||||
name = "github.com/shurcooL/sanitized_anchor_name"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3f53e9e4dfbb664cd62940c9c4b65a2171c66acd0b7621a1a6b8e78513525a52"
|
||||
name = "github.com/sirupsen/logrus"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ad15b42461921f1fb3529b058c6786c6a45d5162"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:4d1f0640875aefefdb2151f297c144518a71f5729c4b9f9423f09df501f699c5"
|
||||
name = "github.com/smallstep/assert"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "de77670473b5492f5d0bce155b5c01534c2d13f7"
|
||||
|
||||
[[projects]]
|
||||
branch = "ssh-login"
|
||||
digest = "1:35cb5bef5983a5e43466c00bd6bd7b3b401a94f4abe22873b7b72bad344ee855"
|
||||
name = "github.com/smallstep/cli"
|
||||
packages = [
|
||||
"command",
|
||||
"command/version",
|
||||
"config",
|
||||
"crypto/keys",
|
||||
"crypto/pemutil",
|
||||
"crypto/randutil",
|
||||
"crypto/tlsutil",
|
||||
"crypto/x509util",
|
||||
"errs",
|
||||
"jose",
|
||||
"pkg/blackfriday",
|
||||
"pkg/x509",
|
||||
"token",
|
||||
"token/provision",
|
||||
"ui",
|
||||
"usage",
|
||||
"utils",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "15911d8625df46378c68ecda3b97290e745d7097"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7d03323edb817ca94efaee5489cde6acd06ceeaca9e6eee106d2d6a90deca997"
|
||||
name = "github.com/smallstep/nosql"
|
||||
packages = [
|
||||
".",
|
||||
"badger",
|
||||
"bolt",
|
||||
"database",
|
||||
"mysql",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f80b3f432de0662f07ebd58fe52b0a119fe5dcd9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc"
|
||||
name = "github.com/spf13/cast"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8c9545af88b134710ab1cd196795e7f2388358d7"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:6743b69de0d73e91004e4e201cf4965b59a0fa5caf6f0ffbe0cb9ee8807738a7"
|
||||
name = "github.com/urfave/cli"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "b67dcf995b6a7b7f14fad5fcb7cc5441b05e814b"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5f7414cf41466d4b4dd7ec52b2cd3e481e08cfd11e7e24fef730c0e483e88bb1"
|
||||
name = "go.etcd.io/bbolt"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "63597a96ec0ad9e6d43c3fc81e809909e0237461"
|
||||
version = "v1.3.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f492081c0e705c98ea169765a0fa60c9cfee2358a2434e96abd5bb6f59dbc190"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"cryptobyte",
|
||||
"cryptobyte/asn1",
|
||||
"curve25519",
|
||||
"ed25519",
|
||||
"ed25519/internal/edwards25519",
|
||||
"internal/chacha20",
|
||||
"internal/subtle",
|
||||
"ocsp",
|
||||
"pbkdf2",
|
||||
"poly1305",
|
||||
"scrypt",
|
||||
"ssh",
|
||||
"ssh/terminal",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "4d3f4d9ffa16a13f451c3b2999e9c49e9750bf06"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:2f7468b0b3fd7d926072f0dcbb6ec81e337278b4e5de639d017e54f785f0b475"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"html",
|
||||
"html/atom",
|
||||
"http/httpguts",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"internal/timeseries",
|
||||
"trace",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "c44066c5c816ec500d459a2a324a753f78531ae0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:417d27a82efb8473554234a282be33d23b0d6adc121e636b55950f913ac071d6"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "9b800f95dbbc54abff0acf7ee32d88ba4e328c89"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:6b4a1c844969280f4d3e36ef4b0762e3522e701c015f688b68ef91c2ea6b5ac7"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
"go/gcexportdata",
|
||||
"go/internal/cgo",
|
||||
"go/internal/gcimporter",
|
||||
"go/packages",
|
||||
"go/types/typeutil",
|
||||
"internal/fastwalk",
|
||||
"internal/gopathwalk",
|
||||
"internal/semver",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "3a10b9bf0a52df7e992a8c3eb712a86d3c896c75"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3"
|
||||
name = "google.golang.org/appengine"
|
||||
packages = ["cloudsql"]
|
||||
pruneopts = "UT"
|
||||
revision = "54a98f90d1c46b7731eb8fb305d2a321c30ef610"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9593bab40e981b1f90b7e07faeab0d09b75fe338880d08880f986a9d3283c53f"
|
||||
name = "gopkg.in/square/go-jose.v2"
|
||||
packages = [
|
||||
".",
|
||||
"cipher",
|
||||
"json",
|
||||
"jwt",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "730df5f748271903322feb182be83b43ebbbe27d"
|
||||
version = "v2.3.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/Masterminds/sprig",
|
||||
"github.com/go-chi/chi",
|
||||
"github.com/newrelic/go-agent",
|
||||
"github.com/pkg/errors",
|
||||
"github.com/rs/xid",
|
||||
"github.com/sirupsen/logrus",
|
||||
"github.com/smallstep/assert",
|
||||
"github.com/smallstep/cli/command",
|
||||
"github.com/smallstep/cli/command/version",
|
||||
"github.com/smallstep/cli/config",
|
||||
"github.com/smallstep/cli/crypto/keys",
|
||||
"github.com/smallstep/cli/crypto/pemutil",
|
||||
"github.com/smallstep/cli/crypto/randutil",
|
||||
"github.com/smallstep/cli/crypto/tlsutil",
|
||||
"github.com/smallstep/cli/crypto/x509util",
|
||||
"github.com/smallstep/cli/errs",
|
||||
"github.com/smallstep/cli/jose",
|
||||
"github.com/smallstep/cli/pkg/x509",
|
||||
"github.com/smallstep/cli/token",
|
||||
"github.com/smallstep/cli/token/provision",
|
||||
"github.com/smallstep/cli/ui",
|
||||
"github.com/smallstep/cli/usage",
|
||||
"github.com/smallstep/cli/utils",
|
||||
"github.com/smallstep/nosql",
|
||||
"github.com/smallstep/nosql/database",
|
||||
"github.com/urfave/cli",
|
||||
"golang.org/x/crypto/ed25519",
|
||||
"golang.org/x/crypto/ocsp",
|
||||
"golang.org/x/crypto/ssh",
|
||||
"golang.org/x/net/http2",
|
||||
"gopkg.in/square/go-jose.v2",
|
||||
"gopkg.in/square/go-jose.v2/jwt",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
63
Gopkg.toml
63
Gopkg.toml
|
@ -1,63 +0,0 @@
|
|||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
ignored = ["github.com/Masterminds/semver/v3"]
|
||||
|
||||
[[override]]
|
||||
name = "gopkg.in/alecthomas/kingpin.v3-unstable"
|
||||
revision = "63abe20a23e29e80bbef8089bd3dee3ac25e5306"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/go-chi/chi"
|
||||
|
||||
[[override]]
|
||||
branch = "ssh-login"
|
||||
name = "github.com/smallstep/cli"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/smallstep/nosql"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/newrelic/go-agent"
|
||||
version = "1.11.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
version = "1.0.6"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/square/go-jose.v2"
|
||||
version = "2.3.1"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/Masterminds/sprig"
|
||||
version = "3.0.0"
|
29
Makefile
29
Makefile
|
@ -17,21 +17,9 @@ all: build test lint
|
|||
#########################################
|
||||
|
||||
bootstra%:
|
||||
$Q which dep || go get github.com/golang/dep/cmd/dep
|
||||
$Q dep ensure
|
||||
$Q GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.18.0
|
||||
|
||||
|
||||
vendor: Gopkg.lock
|
||||
$Q dep ensure
|
||||
|
||||
define VENDOR_BIN_TMPL
|
||||
vendor/bin/$(notdir $(1)): vendor
|
||||
$Q go build -o $$@ ./vendor/$(1)
|
||||
VENDOR_BINS += vendor/bin/$(notdir $(1))
|
||||
endef
|
||||
|
||||
.PHONY: bootstra% vendor
|
||||
.PHONY: bootstra%
|
||||
|
||||
#################################################
|
||||
# Determine the type of `push` and `version`
|
||||
|
@ -64,20 +52,23 @@ DATE := $(shell date -u '+%Y-%m-%d %H:%M UTC')
|
|||
LDFLAGS := -ldflags='-w -X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"'
|
||||
GOFLAGS := CGO_ENABLED=0
|
||||
|
||||
download:
|
||||
$Q go mod download
|
||||
|
||||
build: $(PREFIX)bin/$(BINNAME)
|
||||
@echo "Build Complete!"
|
||||
|
||||
$(PREFIX)bin/$(BINNAME): vendor $(call rwildcard,*.go)
|
||||
$(PREFIX)bin/$(BINNAME): download $(call rwildcard,*.go)
|
||||
$Q mkdir -p $(@D)
|
||||
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG)
|
||||
|
||||
# Target for building without calling dep ensure
|
||||
# Target to force a build of step-ca without running tests
|
||||
simple:
|
||||
$Q mkdir -p bin/
|
||||
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o bin/$(BINNAME) $(LDFLAGS) $(PKG)
|
||||
$Q mkdir -p $(PREFIX)bin
|
||||
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG)
|
||||
@echo "Build Complete!"
|
||||
|
||||
.PHONY: build simple
|
||||
.PHONY: download build simple
|
||||
|
||||
#########################################
|
||||
# Go generate
|
||||
|
@ -134,8 +125,6 @@ uninstall:
|
|||
#########################################
|
||||
|
||||
clean:
|
||||
@echo "You will need to run 'make bootstrap' or 'dep ensure' directly to re-download any dependencies."
|
||||
$Q rm -rf vendor
|
||||
ifneq ($(BINNAME),"")
|
||||
$Q rm -f bin/$(BINNAME)
|
||||
endif
|
||||
|
|
|
@ -128,7 +128,6 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Set("Location", h.Auth.GetLink(acme.AccountLink,
|
||||
acme.URLSafeProvisionerName(prov), true, acc.GetID()))
|
||||
api.JSONStatus(w, acc, httpStatus)
|
||||
return
|
||||
}
|
||||
|
||||
// GetUpdateAccount is the api for updating an ACME account.
|
||||
|
@ -172,7 +171,6 @@ func (h *Handler) GetUpdateAccount(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
w.Header().Set("Location", h.Auth.GetLink(acme.AccountLink, acme.URLSafeProvisionerName(prov), true, acc.GetID()))
|
||||
api.JSON(w, acc)
|
||||
return
|
||||
}
|
||||
|
||||
func logOrdersByAccount(w http.ResponseWriter, oids []string) {
|
||||
|
@ -209,5 +207,4 @@ func (h *Handler) GetOrdersByAccount(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
api.JSON(w, orders)
|
||||
logOrdersByAccount(w, orders)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -113,7 +113,6 @@ func (h *Handler) GetNonce(w http.ResponseWriter, r *http.Request) {
|
|||
} else {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetDirectory is the ACME resource for returning a directory configuration
|
||||
|
@ -126,7 +125,6 @@ func (h *Handler) GetDirectory(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
dir := h.Auth.GetDirectory(prov)
|
||||
api.JSON(w, dir)
|
||||
return
|
||||
}
|
||||
|
||||
// GetAuthz ACME api for retrieving an Authz.
|
||||
|
@ -149,7 +147,6 @@ func (h *Handler) GetAuthz(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
w.Header().Set("Location", h.Auth.GetLink(acme.AuthzLink, acme.URLSafeProvisionerName(prov), true, authz.GetID()))
|
||||
api.JSON(w, authz)
|
||||
return
|
||||
}
|
||||
|
||||
// GetChallenge ACME api for retrieving a Challenge.
|
||||
|
@ -191,7 +188,6 @@ func (h *Handler) GetChallenge(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Add("Link", link(getLink(acme.AuthzLink, acme.URLSafeProvisionerName(prov), true, ch.GetAuthzID()), "up"))
|
||||
w.Header().Set("Location", getLink(acme.ChallengeLink, acme.URLSafeProvisionerName(prov), true, ch.GetID()))
|
||||
api.JSON(w, ch)
|
||||
return
|
||||
}
|
||||
|
||||
// GetCertificate ACME api for retrieving a Certificate.
|
||||
|
@ -210,5 +206,4 @@ func (h *Handler) GetCertificate(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
w.Header().Set("Content-Type", "application/pem-certificate-chain; charset=utf-8")
|
||||
w.Write(certBytes)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/acme"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
)
|
||||
|
@ -230,7 +231,8 @@ func TestHandlerGetNonce(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestHandlerGetDirectory(t *testing.T) {
|
||||
auth := acme.NewAuthority(nil, "ca.smallstep.com", "acme", nil)
|
||||
auth, err := acme.NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
prov := newProv()
|
||||
url := fmt.Sprintf("http://ca.smallstep.com/acme/%s/directory", acme.URLSafeProvisionerName(prov))
|
||||
|
||||
|
|
|
@ -42,7 +42,6 @@ func (h *Handler) addNonce(next nextHTTP) nextHTTP {
|
|||
w.Header().Set("Cache-Control", "no-store")
|
||||
logNonce(w, nonce)
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,7 +56,6 @@ func (h *Handler) addDirLink(next nextHTTP) nextHTTP {
|
|||
}
|
||||
w.Header().Add("Link", link(h.Auth.GetLink(acme.DirectoryLink, acme.URLSafeProvisionerName(prov), true), "index"))
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,7 +85,6 @@ func (h *Handler) verifyContentType(next nextHTTP) nextHTTP {
|
|||
}
|
||||
api.WriteError(w, acme.MalformedErr(errors.Errorf(
|
||||
"expected content-type to be in %s, but got %s", expected, ct)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +103,6 @@ func (h *Handler) parseJWS(next nextHTTP) nextHTTP {
|
|||
}
|
||||
ctx := context.WithValue(r.Context(), jwsContextKey, jws)
|
||||
next(w, r.WithContext(ctx))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,7 +198,6 @@ func (h *Handler) validateJWS(next nextHTTP) nextHTTP {
|
|||
return
|
||||
}
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,7 +243,6 @@ func (h *Handler) extractJWK(next nextHTTP) nextHTTP {
|
|||
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||
}
|
||||
next(w, r.WithContext(ctx))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,7 +269,6 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP {
|
|||
}
|
||||
ctx = context.WithValue(ctx, provisionerContextKey, p)
|
||||
next(w, r.WithContext(ctx))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -355,7 +348,6 @@ func (h *Handler) verifyAndExtractJWSPayload(next nextHTTP) nextHTTP {
|
|||
isEmptyJSON: string(payload) == "{}",
|
||||
})
|
||||
next(w, r.WithContext(ctx))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -372,6 +364,5 @@ func (h *Handler) isPostAsGet(next nextHTTP) nextHTTP {
|
|||
return
|
||||
}
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ var testBody = []byte("foo")
|
|||
|
||||
func testNext(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(testBody)
|
||||
return
|
||||
}
|
||||
|
||||
func TestHandlerAddNonce(t *testing.T) {
|
||||
|
@ -471,7 +470,6 @@ func TestHandlerParseJWS(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
assert.Equals(t, gotRaw, expRaw)
|
||||
w.Write(testBody)
|
||||
return
|
||||
},
|
||||
statusCode: 200,
|
||||
}
|
||||
|
@ -923,7 +921,6 @@ func TestHandlerLookupJWK(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
assert.Equals(t, _jwk, jwk)
|
||||
w.Write(testBody)
|
||||
return
|
||||
},
|
||||
statusCode: 200,
|
||||
}
|
||||
|
@ -1114,7 +1111,6 @@ func TestHandlerExtractJWK(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
assert.Equals(t, _jwk.KeyID, pub.KeyID)
|
||||
w.Write(testBody)
|
||||
return
|
||||
},
|
||||
statusCode: 200,
|
||||
}
|
||||
|
@ -1139,7 +1135,6 @@ func TestHandlerExtractJWK(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
assert.Equals(t, _jwk.KeyID, pub.KeyID)
|
||||
w.Write(testBody)
|
||||
return
|
||||
},
|
||||
statusCode: 200,
|
||||
}
|
||||
|
@ -1448,7 +1443,6 @@ func TestHandlerValidateJWS(t *testing.T) {
|
|||
ctx: context.WithValue(context.Background(), jwsContextKey, jws),
|
||||
next: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(testBody)
|
||||
return
|
||||
},
|
||||
statusCode: 200,
|
||||
}
|
||||
|
@ -1479,7 +1473,6 @@ func TestHandlerValidateJWS(t *testing.T) {
|
|||
ctx: context.WithValue(context.Background(), jwsContextKey, jws),
|
||||
next: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(testBody)
|
||||
return
|
||||
},
|
||||
statusCode: 200,
|
||||
}
|
||||
|
@ -1510,7 +1503,6 @@ func TestHandlerValidateJWS(t *testing.T) {
|
|||
ctx: context.WithValue(context.Background(), jwsContextKey, jws),
|
||||
next: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(testBody)
|
||||
return
|
||||
},
|
||||
statusCode: 200,
|
||||
}
|
||||
|
|
|
@ -97,7 +97,6 @@ func (h *Handler) NewOrder(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
w.Header().Set("Location", h.Auth.GetLink(acme.OrderLink, acme.URLSafeProvisionerName(prov), true, o.GetID()))
|
||||
api.JSONStatus(w, o, http.StatusCreated)
|
||||
return
|
||||
}
|
||||
|
||||
// GetOrder ACME api for retrieving an order.
|
||||
|
@ -121,7 +120,6 @@ func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
w.Header().Set("Location", h.Auth.GetLink(acme.OrderLink, acme.URLSafeProvisionerName(prov), true, o.GetID()))
|
||||
api.JSON(w, o)
|
||||
return
|
||||
}
|
||||
|
||||
// FinalizeOrder attemptst to finalize an order and create a certificate.
|
||||
|
@ -160,5 +158,4 @@ func (h *Handler) FinalizeOrder(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
w.Header().Set("Location", h.Auth.GetLink(acme.OrderLink, acme.URLSafeProvisionerName(prov), true, o.ID))
|
||||
api.JSON(w, o)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
database "github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"github.com/smallstep/nosql"
|
||||
)
|
||||
|
@ -43,11 +44,35 @@ type Authority struct {
|
|||
signAuth SignAuthority
|
||||
}
|
||||
|
||||
var (
|
||||
accountTable = []byte("acme_accounts")
|
||||
accountByKeyIDTable = []byte("acme_keyID_accountID_index")
|
||||
authzTable = []byte("acme_authzs")
|
||||
challengeTable = []byte("acme_challenges")
|
||||
nonceTable = []byte("nonces")
|
||||
orderTable = []byte("acme_orders")
|
||||
ordersByAccountIDTable = []byte("acme_account-orders-index")
|
||||
certTable = []byte("acme_certs")
|
||||
)
|
||||
|
||||
// NewAuthority returns a new Authority that implements the ACME interface.
|
||||
func NewAuthority(db nosql.DB, dns, prefix string, signAuth SignAuthority) *Authority {
|
||||
func NewAuthority(db nosql.DB, dns, prefix string, signAuth SignAuthority) (*Authority, error) {
|
||||
if _, ok := db.(*database.SimpleDB); !ok {
|
||||
// If it's not a SimpleDB then go ahead and bootstrap the DB with the
|
||||
// necessary ACME tables. SimpleDB should ONLY be used for testing.
|
||||
tables := [][]byte{accountTable, accountByKeyIDTable, authzTable,
|
||||
challengeTable, nonceTable, orderTable, ordersByAccountIDTable,
|
||||
certTable}
|
||||
for _, b := range tables {
|
||||
if err := db.CreateTable(b); err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating table %s",
|
||||
string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
return &Authority{
|
||||
db: db, dir: newDirectory(dns, prefix), signAuth: signAuth,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetLink returns the requested link from the directory.
|
||||
|
|
|
@ -14,7 +14,8 @@ import (
|
|||
)
|
||||
|
||||
func TestAuthorityGetLink(t *testing.T) {
|
||||
auth := NewAuthority(nil, "ca.smallstep.com", "acme", nil)
|
||||
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
provID := "acme-test-provisioner"
|
||||
type test struct {
|
||||
auth *Authority
|
||||
|
@ -69,7 +70,8 @@ func TestAuthorityGetLink(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAuthorityGetDirectory(t *testing.T) {
|
||||
auth := NewAuthority(nil, "ca.smallstep.com", "acme", nil)
|
||||
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
prov := newProv()
|
||||
acmeDir := auth.GetDirectory(prov)
|
||||
assert.Equals(t, acmeDir.NewNonce, fmt.Sprintf("https://ca.smallstep.com/acme/%s/new-nonce", URLSafeProvisionerName(prov)))
|
||||
|
@ -88,11 +90,12 @@ func TestAuthorityNewNonce(t *testing.T) {
|
|||
}
|
||||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/newNonce-error": func(t *testing.T) test {
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
||||
return nil, false, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
res: nil,
|
||||
|
@ -102,12 +105,13 @@ func TestAuthorityNewNonce(t *testing.T) {
|
|||
"ok": func(t *testing.T) test {
|
||||
var _res string
|
||||
res := &_res
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
||||
*res = string(key)
|
||||
return nil, true, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
res: res,
|
||||
|
@ -141,22 +145,24 @@ func TestAuthorityUseNonce(t *testing.T) {
|
|||
}
|
||||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/newNonce-error": func(t *testing.T) test {
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MUpdate: func(tx *database.Tx) error {
|
||||
return errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
err: ServerInternalErr(errors.New("error deleting nonce foo: force")),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MUpdate: func(tx *database.Tx) error {
|
||||
return nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
}
|
||||
|
@ -195,11 +201,12 @@ func TestAuthorityNewAccount(t *testing.T) {
|
|||
}
|
||||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/newAccount-error": func(t *testing.T) test {
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
||||
return nil, false, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
ops: ops,
|
||||
|
@ -213,7 +220,7 @@ func TestAuthorityNewAccount(t *testing.T) {
|
|||
count = 0
|
||||
dir = newDirectory("ca.smallstep.com", "acme")
|
||||
)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
||||
if count == 1 {
|
||||
var acc *account
|
||||
|
@ -225,6 +232,7 @@ func TestAuthorityNewAccount(t *testing.T) {
|
|||
return nil, true, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
ops: ops,
|
||||
|
@ -267,13 +275,14 @@ func TestAuthorityGetAccount(t *testing.T) {
|
|||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/getAccount-error": func(t *testing.T) test {
|
||||
id := "foo"
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, accountTable)
|
||||
assert.Equals(t, key, []byte(id))
|
||||
return nil, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: id,
|
||||
|
@ -285,11 +294,12 @@ func TestAuthorityGetAccount(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
b, err := json.Marshal(acc)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
return b, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: acc.ID,
|
||||
|
@ -338,7 +348,8 @@ func TestAuthorityGetAccountByKey(t *testing.T) {
|
|||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
jwk.Key = "foo"
|
||||
auth := NewAuthority(nil, "ca.smallstep.com", "acme", nil)
|
||||
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
jwk: jwk,
|
||||
|
@ -350,13 +361,14 @@ func TestAuthorityGetAccountByKey(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
kid, err := keyToID(jwk)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, accountByKeyIDTable)
|
||||
assert.Equals(t, key, []byte(kid))
|
||||
return nil, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
jwk: jwk,
|
||||
|
@ -371,7 +383,7 @@ func TestAuthorityGetAccountByKey(t *testing.T) {
|
|||
count := 0
|
||||
kid, err := keyToID(acc.Key)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
var ret []byte
|
||||
switch {
|
||||
|
@ -388,6 +400,7 @@ func TestAuthorityGetAccountByKey(t *testing.T) {
|
|||
return ret, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
jwk: acc.Key,
|
||||
|
@ -434,13 +447,14 @@ func TestAuthorityGetOrder(t *testing.T) {
|
|||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/getOrder-error": func(t *testing.T) test {
|
||||
id := "foo"
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, orderTable)
|
||||
assert.Equals(t, key, []byte(id))
|
||||
return nil, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: id,
|
||||
|
@ -452,13 +466,14 @@ func TestAuthorityGetOrder(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
b, err := json.Marshal(o)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, orderTable)
|
||||
assert.Equals(t, key, []byte(o.ID))
|
||||
return b, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: o.ID,
|
||||
|
@ -472,7 +487,7 @@ func TestAuthorityGetOrder(t *testing.T) {
|
|||
b, err := json.Marshal(o)
|
||||
assert.FatalError(t, err)
|
||||
i := 0
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
switch {
|
||||
case i == 0:
|
||||
|
@ -487,6 +502,7 @@ func TestAuthorityGetOrder(t *testing.T) {
|
|||
}
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: o.ID,
|
||||
|
@ -500,13 +516,14 @@ func TestAuthorityGetOrder(t *testing.T) {
|
|||
o.Status = "valid"
|
||||
b, err := json.Marshal(o)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, orderTable)
|
||||
assert.Equals(t, key, []byte(o.ID))
|
||||
return b, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: o.ID,
|
||||
|
@ -553,13 +570,14 @@ func TestAuthorityGetCertificate(t *testing.T) {
|
|||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/getCertificate-error": func(t *testing.T) test {
|
||||
id := "foo"
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, certTable)
|
||||
assert.Equals(t, key, []byte(id))
|
||||
return nil, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: id,
|
||||
|
@ -571,13 +589,14 @@ func TestAuthorityGetCertificate(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
b, err := json.Marshal(cert)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, certTable)
|
||||
assert.Equals(t, key, []byte(cert.ID))
|
||||
return b, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: cert.ID,
|
||||
|
@ -590,13 +609,14 @@ func TestAuthorityGetCertificate(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
b, err := json.Marshal(cert)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, certTable)
|
||||
assert.Equals(t, key, []byte(cert.ID))
|
||||
return b, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: cert.ID,
|
||||
|
@ -644,13 +664,14 @@ func TestAuthorityGetAuthz(t *testing.T) {
|
|||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/getAuthz-error": func(t *testing.T) test {
|
||||
id := "foo"
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, authzTable)
|
||||
assert.Equals(t, key, []byte(id))
|
||||
return nil, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: id,
|
||||
|
@ -662,13 +683,14 @@ func TestAuthorityGetAuthz(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
b, err := json.Marshal(az)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, authzTable)
|
||||
assert.Equals(t, key, []byte(az.getID()))
|
||||
return b, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: az.getID(),
|
||||
|
@ -682,7 +704,7 @@ func TestAuthorityGetAuthz(t *testing.T) {
|
|||
b, err := json.Marshal(az)
|
||||
assert.FatalError(t, err)
|
||||
count := 0
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
var ret []byte
|
||||
switch count {
|
||||
|
@ -699,6 +721,7 @@ func TestAuthorityGetAuthz(t *testing.T) {
|
|||
return ret, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: az.getID(),
|
||||
|
@ -757,7 +780,7 @@ func TestAuthorityGetAuthz(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
|
||||
count = 0
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
var ret []byte
|
||||
switch count {
|
||||
|
@ -778,6 +801,7 @@ func TestAuthorityGetAuthz(t *testing.T) {
|
|||
return ret, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: az.getID(),
|
||||
|
@ -822,11 +846,12 @@ func TestAuthorityNewOrder(t *testing.T) {
|
|||
}
|
||||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/newOrder-error": func(t *testing.T) test {
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
||||
return nil, false, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
ops: defaultOrderOps(),
|
||||
|
@ -843,7 +868,7 @@ func TestAuthorityNewOrder(t *testing.T) {
|
|||
_accID string
|
||||
accID = &_accID
|
||||
)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
||||
switch count {
|
||||
case 0:
|
||||
|
@ -876,6 +901,7 @@ func TestAuthorityNewOrder(t *testing.T) {
|
|||
return nil, database.ErrNotFound
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
ops: defaultOrderOps(),
|
||||
|
@ -918,13 +944,14 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) {
|
|||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/getOrderIDsByAccount-error": func(t *testing.T) test {
|
||||
id := "foo"
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, ordersByAccountIDTable)
|
||||
assert.Equals(t, key, []byte(id))
|
||||
return nil, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: id,
|
||||
|
@ -938,7 +965,7 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) {
|
|||
count = 0
|
||||
err error
|
||||
)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
var ret []byte
|
||||
switch count {
|
||||
|
@ -956,6 +983,7 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) {
|
|||
return ret, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: id,
|
||||
|
@ -973,7 +1001,7 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) {
|
|||
baz, err := newO()
|
||||
bar.Status = StatusInvalid
|
||||
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
var ret []byte
|
||||
switch count {
|
||||
|
@ -1002,6 +1030,7 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) {
|
|||
return ret, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: id,
|
||||
|
@ -1043,13 +1072,14 @@ func TestAuthorityFinalizeOrder(t *testing.T) {
|
|||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/getOrder-error": func(t *testing.T) test {
|
||||
id := "foo"
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, orderTable)
|
||||
assert.Equals(t, key, []byte(id))
|
||||
return nil, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: id,
|
||||
|
@ -1061,13 +1091,14 @@ func TestAuthorityFinalizeOrder(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
b, err := json.Marshal(o)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, orderTable)
|
||||
assert.Equals(t, key, []byte(o.ID))
|
||||
return b, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: o.ID,
|
||||
|
@ -1081,7 +1112,7 @@ func TestAuthorityFinalizeOrder(t *testing.T) {
|
|||
o.Expires = time.Now().Add(-time.Minute)
|
||||
b, err := json.Marshal(o)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, orderTable)
|
||||
assert.Equals(t, key, []byte(o.ID))
|
||||
|
@ -1093,6 +1124,7 @@ func TestAuthorityFinalizeOrder(t *testing.T) {
|
|||
return nil, false, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: o.ID,
|
||||
|
@ -1107,13 +1139,14 @@ func TestAuthorityFinalizeOrder(t *testing.T) {
|
|||
o.Certificate = "certID"
|
||||
b, err := json.Marshal(o)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, orderTable)
|
||||
assert.Equals(t, key, []byte(o.ID))
|
||||
return b, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: o.ID,
|
||||
|
@ -1161,13 +1194,14 @@ func TestAuthorityValidateChallenge(t *testing.T) {
|
|||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/getChallenge-error": func(t *testing.T) test {
|
||||
id := "foo"
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, challengeTable)
|
||||
assert.Equals(t, key, []byte(id))
|
||||
return nil, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: id,
|
||||
|
@ -1179,13 +1213,14 @@ func TestAuthorityValidateChallenge(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
b, err := json.Marshal(ch)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, challengeTable)
|
||||
assert.Equals(t, key, []byte(ch.getID()))
|
||||
return b, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: ch.getID(),
|
||||
|
@ -1198,7 +1233,7 @@ func TestAuthorityValidateChallenge(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
b, err := json.Marshal(ch)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, challengeTable)
|
||||
assert.Equals(t, key, []byte(ch.getID()))
|
||||
|
@ -1210,6 +1245,7 @@ func TestAuthorityValidateChallenge(t *testing.T) {
|
|||
return nil, false, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: ch.getID(),
|
||||
|
@ -1226,13 +1262,14 @@ func TestAuthorityValidateChallenge(t *testing.T) {
|
|||
_ch.baseChallenge.Validated = clock.Now()
|
||||
b, err := json.Marshal(ch)
|
||||
assert.FatalError(t, err)
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, challengeTable)
|
||||
assert.Equals(t, key, []byte(ch.getID()))
|
||||
return b, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: ch.getID(),
|
||||
|
@ -1282,13 +1319,14 @@ func TestAuthorityUpdateAccount(t *testing.T) {
|
|||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/getAccount-error": func(t *testing.T) test {
|
||||
id := "foo"
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, accountTable)
|
||||
assert.Equals(t, key, []byte(id))
|
||||
return nil, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: id,
|
||||
|
@ -1302,7 +1340,7 @@ func TestAuthorityUpdateAccount(t *testing.T) {
|
|||
b, err := json.Marshal(acc)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
return b, nil
|
||||
},
|
||||
|
@ -1310,6 +1348,7 @@ func TestAuthorityUpdateAccount(t *testing.T) {
|
|||
return nil, false, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: acc.ID,
|
||||
|
@ -1327,7 +1366,7 @@ func TestAuthorityUpdateAccount(t *testing.T) {
|
|||
_acc := *acc
|
||||
clone := &_acc
|
||||
clone.Contact = contact
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
return b, nil
|
||||
},
|
||||
|
@ -1337,6 +1376,7 @@ func TestAuthorityUpdateAccount(t *testing.T) {
|
|||
return nil, true, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: acc.ID,
|
||||
|
@ -1384,13 +1424,14 @@ func TestAuthorityDeactivateAccount(t *testing.T) {
|
|||
tests := map[string]func(t *testing.T) test{
|
||||
"fail/getAccount-error": func(t *testing.T) test {
|
||||
id := "foo"
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equals(t, bucket, accountTable)
|
||||
assert.Equals(t, key, []byte(id))
|
||||
return nil, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: id,
|
||||
|
@ -1403,7 +1444,7 @@ func TestAuthorityDeactivateAccount(t *testing.T) {
|
|||
b, err := json.Marshal(acc)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
return b, nil
|
||||
},
|
||||
|
@ -1411,6 +1452,7 @@ func TestAuthorityDeactivateAccount(t *testing.T) {
|
|||
return nil, false, errors.New("force")
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: acc.ID,
|
||||
|
@ -1428,7 +1470,7 @@ func TestAuthorityDeactivateAccount(t *testing.T) {
|
|||
clone := &_acc
|
||||
clone.Status = StatusDeactivated
|
||||
clone.Deactivated = clock.Now()
|
||||
auth := NewAuthority(&db.MockNoSQLDB{
|
||||
auth, err := NewAuthority(&db.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
return b, nil
|
||||
},
|
||||
|
@ -1438,6 +1480,7 @@ func TestAuthorityDeactivateAccount(t *testing.T) {
|
|||
return nil, true, nil
|
||||
},
|
||||
}, "ca.smallstep.com", "acme", nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
auth: auth,
|
||||
id: acc.ID,
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
// SignAuthority is the interface implemented by a CA authority.
|
||||
type SignAuthority interface {
|
||||
Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error)
|
||||
Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||
LoadProvisionerByID(string) (provisioner.Interface, error)
|
||||
}
|
||||
|
||||
|
@ -22,17 +22,6 @@ type Identifier struct {
|
|||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
var (
|
||||
accountTable = []byte("acme-accounts")
|
||||
accountByKeyIDTable = []byte("acme-keyID-accountID-index")
|
||||
authzTable = []byte("acme-authzs")
|
||||
challengeTable = []byte("acme-challenges")
|
||||
nonceTable = []byte("nonce-table")
|
||||
orderTable = []byte("acme-orders")
|
||||
ordersByAccountIDTable = []byte("acme-account-orders-index")
|
||||
certTable = []byte("acme-certs")
|
||||
)
|
||||
|
||||
var (
|
||||
// StatusValid -- valid
|
||||
StatusValid = "valid"
|
||||
|
|
|
@ -73,11 +73,11 @@ func newOrder(db nosql.DB, ops OrderOptions) (*order, error) {
|
|||
|
||||
authzs := make([]string, len(ops.Identifiers))
|
||||
for i, identifier := range ops.Identifiers {
|
||||
authz, err := newAuthz(db, ops.AccountID, identifier)
|
||||
az, err := newAuthz(db, ops.AccountID, identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authzs[i] = authz.getID()
|
||||
authzs[i] = az.getID()
|
||||
}
|
||||
|
||||
now := clock.Now()
|
||||
|
@ -203,14 +203,14 @@ func (o *order) updateStatus(db nosql.DB) (*order, error) {
|
|||
StatusPending: 0,
|
||||
}
|
||||
for _, azID := range o.Authorizations {
|
||||
authz, err := getAuthz(db, azID)
|
||||
az, err := getAuthz(db, azID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if authz, err = authz.updateStatus(db); err != nil {
|
||||
if az, err = az.updateStatus(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st := authz.getStatus()
|
||||
st := az.getStatus()
|
||||
count[st]++
|
||||
}
|
||||
switch {
|
||||
|
@ -274,7 +274,7 @@ func (o *order) finalize(db nosql.DB, csr *x509.CertificateRequest, auth SignAut
|
|||
}
|
||||
|
||||
// Create and store a new certificate.
|
||||
leaf, inter, err := auth.Sign(csr, provisioner.Options{
|
||||
certChain, err := auth.Sign(csr, provisioner.Options{
|
||||
NotBefore: provisioner.NewTimeDuration(o.NotBefore),
|
||||
NotAfter: provisioner.NewTimeDuration(o.NotAfter),
|
||||
}, signOps...)
|
||||
|
@ -285,8 +285,8 @@ func (o *order) finalize(db nosql.DB, csr *x509.CertificateRequest, auth SignAut
|
|||
cert, err := newCert(db, CertOptions{
|
||||
AccountID: o.AccountID,
|
||||
OrderID: o.ID,
|
||||
Leaf: leaf,
|
||||
Intermediates: []*x509.Certificate{inter},
|
||||
Leaf: certChain[0],
|
||||
Intermediates: certChain[1:],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -789,19 +789,19 @@ func TestOrderUpdateStatus(t *testing.T) {
|
|||
}
|
||||
|
||||
type mockSignAuth struct {
|
||||
sign func(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error)
|
||||
sign func(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||
loadProvisionerByID func(string) (provisioner.Interface, error)
|
||||
ret1, ret2 interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) {
|
||||
func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
|
||||
if m.sign != nil {
|
||||
return m.sign(csr, signOpts, extraOpts...)
|
||||
} else if m.err != nil {
|
||||
return nil, nil, m.err
|
||||
return nil, m.err
|
||||
}
|
||||
return m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate), m.err
|
||||
return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err
|
||||
}
|
||||
|
||||
func (m *mockSignAuth) LoadProvisionerByID(id string) (provisioner.Interface, error) {
|
||||
|
@ -1082,9 +1082,9 @@ func TestOrderFinalize(t *testing.T) {
|
|||
res: clone,
|
||||
csr: csr,
|
||||
sa: &mockSignAuth{
|
||||
sign: func(csr *x509.CertificateRequest, pops provisioner.Options, signOps ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) {
|
||||
sign: func(csr *x509.CertificateRequest, pops provisioner.Options, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) {
|
||||
assert.Equals(t, len(signOps), 4)
|
||||
return crt, inter, nil
|
||||
return []*x509.Certificate{crt, inter}, nil
|
||||
},
|
||||
},
|
||||
db: &db.MockNoSQLDB{
|
||||
|
|
42
api/api.go
42
api/api.go
|
@ -33,8 +33,8 @@ type Authority interface {
|
|||
AuthorizeSign(ott string) ([]provisioner.SignOption, error)
|
||||
GetTLSOptions() *tlsutil.TLSOptions
|
||||
Root(shasum string) (*x509.Certificate, error)
|
||||
Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error)
|
||||
Renew(peer *x509.Certificate) (*x509.Certificate, *x509.Certificate, error)
|
||||
Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||
Renew(peer *x509.Certificate) ([]*x509.Certificate, error)
|
||||
LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error)
|
||||
LoadProvisionerByID(string) (provisioner.Interface, error)
|
||||
GetProvisioners(cursor string, limit int) (provisioner.List, string, error)
|
||||
|
@ -213,6 +213,7 @@ func (s *SignRequest) Validate() error {
|
|||
type SignResponse struct {
|
||||
ServerPEM Certificate `json:"crt"`
|
||||
CaPEM Certificate `json:"ca"`
|
||||
CertChainPEM []Certificate `json:"certChain"`
|
||||
TLSOptions *tlsutil.TLSOptions `json:"tlsOptions,omitempty"`
|
||||
TLS *tls.ConnectionState `json:"-"`
|
||||
}
|
||||
|
@ -282,6 +283,14 @@ func (h *caHandler) Root(w http.ResponseWriter, r *http.Request) {
|
|||
JSON(w, &RootResponse{RootPEM: Certificate{cert}})
|
||||
}
|
||||
|
||||
func certChainToPEM(certChain []*x509.Certificate) []Certificate {
|
||||
certChainPEM := make([]Certificate, 0, len(certChain))
|
||||
for _, c := range certChain {
|
||||
certChainPEM = append(certChainPEM, Certificate{c})
|
||||
}
|
||||
return certChainPEM
|
||||
}
|
||||
|
||||
// Sign is an HTTP handler that reads a certificate request and an
|
||||
// one-time-token (ott) from the body and creates a new certificate with the
|
||||
// information in the certificate request.
|
||||
|
@ -309,16 +318,21 @@ func (h *caHandler) Sign(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
cert, root, err := h.Authority.Sign(body.CsrPEM.CertificateRequest, opts, signOpts...)
|
||||
certChain, err := h.Authority.Sign(body.CsrPEM.CertificateRequest, opts, signOpts...)
|
||||
if err != nil {
|
||||
WriteError(w, Forbidden(err))
|
||||
return
|
||||
}
|
||||
|
||||
logCertificate(w, cert)
|
||||
certChainPEM := certChainToPEM(certChain)
|
||||
var caPEM Certificate
|
||||
if len(certChainPEM) > 0 {
|
||||
caPEM = certChainPEM[1]
|
||||
}
|
||||
logCertificate(w, certChain[0])
|
||||
JSONStatus(w, &SignResponse{
|
||||
ServerPEM: Certificate{cert},
|
||||
CaPEM: Certificate{root},
|
||||
ServerPEM: certChainPEM[0],
|
||||
CaPEM: caPEM,
|
||||
CertChainPEM: certChainPEM,
|
||||
TLSOptions: h.Authority.GetTLSOptions(),
|
||||
}, http.StatusCreated)
|
||||
}
|
||||
|
@ -331,16 +345,22 @@ func (h *caHandler) Renew(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
cert, root, err := h.Authority.Renew(r.TLS.PeerCertificates[0])
|
||||
certChain, err := h.Authority.Renew(r.TLS.PeerCertificates[0])
|
||||
if err != nil {
|
||||
WriteError(w, Forbidden(err))
|
||||
return
|
||||
}
|
||||
certChainPEM := certChainToPEM(certChain)
|
||||
var caPEM Certificate
|
||||
if len(certChainPEM) > 0 {
|
||||
caPEM = certChainPEM[1]
|
||||
}
|
||||
|
||||
logCertificate(w, cert)
|
||||
logCertificate(w, certChain[0])
|
||||
JSONStatus(w, &SignResponse{
|
||||
ServerPEM: Certificate{cert},
|
||||
CaPEM: Certificate{root},
|
||||
ServerPEM: certChainPEM[0],
|
||||
CaPEM: caPEM,
|
||||
CertChainPEM: certChainPEM,
|
||||
TLSOptions: h.Authority.GetTLSOptions(),
|
||||
}, http.StatusCreated)
|
||||
}
|
||||
|
|
|
@ -502,10 +502,10 @@ type mockAuthority struct {
|
|||
authorizeSign func(ott string) ([]provisioner.SignOption, error)
|
||||
getTLSOptions func() *tlsutil.TLSOptions
|
||||
root func(shasum string) (*x509.Certificate, error)
|
||||
sign func(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error)
|
||||
sign func(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||
signSSH func(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
|
||||
signSSHAddUser func(key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
|
||||
renew func(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error)
|
||||
renew func(cert *x509.Certificate) ([]*x509.Certificate, error)
|
||||
loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, error)
|
||||
loadProvisionerByID func(provID string) (provisioner.Interface, error)
|
||||
getProvisioners func(nextCursor string, limit int) (provisioner.List, string, error)
|
||||
|
@ -545,11 +545,11 @@ func (m *mockAuthority) Root(shasum string) (*x509.Certificate, error) {
|
|||
return m.ret1.(*x509.Certificate), m.err
|
||||
}
|
||||
|
||||
func (m *mockAuthority) Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) {
|
||||
func (m *mockAuthority) Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
|
||||
if m.sign != nil {
|
||||
return m.sign(cr, opts, signOpts...)
|
||||
}
|
||||
return m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate), m.err
|
||||
return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err
|
||||
}
|
||||
|
||||
func (m *mockAuthority) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
|
||||
|
@ -566,11 +566,11 @@ func (m *mockAuthority) SignSSHAddUser(key ssh.PublicKey, cert *ssh.Certificate)
|
|||
return m.ret1.(*ssh.Certificate), m.err
|
||||
}
|
||||
|
||||
func (m *mockAuthority) Renew(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) {
|
||||
func (m *mockAuthority) Renew(cert *x509.Certificate) ([]*x509.Certificate, error) {
|
||||
if m.renew != nil {
|
||||
return m.renew(cert)
|
||||
}
|
||||
return m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate), m.err
|
||||
return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err
|
||||
}
|
||||
|
||||
func (m *mockAuthority) GetProvisioners(nextCursor string, limit int) (provisioner.List, string, error) {
|
||||
|
@ -757,8 +757,8 @@ func Test_caHandler_Sign(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected1 := []byte(`{"crt":"` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"}`)
|
||||
expected2 := []byte(`{"crt":"` + strings.Replace(stepCertPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"}`)
|
||||
expected1 := []byte(`{"crt":"` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n","certChain":["` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"]}`)
|
||||
expected2 := []byte(`{"crt":"` + strings.Replace(stepCertPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n","certChain":["` + strings.Replace(stepCertPEM, "\n", `\n`, -1) + `\n","` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"]}`)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -831,7 +831,7 @@ func Test_caHandler_Renew(t *testing.T) {
|
|||
{"renew error", cs, nil, nil, fmt.Errorf("an error"), http.StatusForbidden},
|
||||
}
|
||||
|
||||
expected := []byte(`{"crt":"` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"}`)
|
||||
expected := []byte(`{"crt":"` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n","certChain":["` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"]}`)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -69,10 +69,12 @@ func (p *ACME) AuthorizeSign(ctx context.Context, _ string) ([]SignOption, error
|
|||
return nil, errors.Errorf("unexpected method type %d in context", m)
|
||||
}
|
||||
return []SignOption{
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
// modifiers / withOptions
|
||||
newProvisionerExtensionOption(TypeACME, p.Name, ""),
|
||||
newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()),
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
// validators
|
||||
defaultPublicKeyValidator{},
|
||||
newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -155,28 +155,23 @@ func TestACME_AuthorizeSign(t *testing.T) {
|
|||
if assert.NotNil(t, got) {
|
||||
assert.Len(t, 4, got)
|
||||
|
||||
_pdd := got[0]
|
||||
pdd, ok := _pdd.(profileDefaultDuration)
|
||||
assert.True(t, ok)
|
||||
assert.Equals(t, pdd, profileDefaultDuration(86400000000000))
|
||||
|
||||
_peo := got[1]
|
||||
peo, ok := _peo.(*provisionerExtensionOption)
|
||||
assert.True(t, ok)
|
||||
assert.Equals(t, peo.Type, 6)
|
||||
assert.Equals(t, peo.Name, "test@acme-provisioner.com")
|
||||
assert.Equals(t, peo.CredentialID, "")
|
||||
assert.Equals(t, peo.KeyValuePairs, nil)
|
||||
|
||||
_vv := got[2]
|
||||
vv, ok := _vv.(*validityValidator)
|
||||
assert.True(t, ok)
|
||||
assert.Equals(t, vv.min, time.Duration(300000000000))
|
||||
assert.Equals(t, vv.max, time.Duration(86400000000000))
|
||||
|
||||
_dpkv := got[3]
|
||||
_, ok = _dpkv.(defaultPublicKeyValidator)
|
||||
assert.True(t, ok)
|
||||
for _, o := range got {
|
||||
switch v := o.(type) {
|
||||
case *provisionerExtensionOption:
|
||||
assert.Equals(t, v.Type, int(TypeACME))
|
||||
assert.Equals(t, v.Name, tt.prov.GetName())
|
||||
assert.Equals(t, v.CredentialID, "")
|
||||
assert.Len(t, 0, v.KeyValuePairs)
|
||||
case profileDefaultDuration:
|
||||
assert.Equals(t, time.Duration(v), tt.prov.claimer.DefaultTLSCertDuration())
|
||||
case defaultPublicKeyValidator:
|
||||
case *validityValidator:
|
||||
assert.Equals(t, v.min, tt.prov.claimer.MinTLSCertDuration())
|
||||
assert.Equals(t, v.max, tt.prov.claimer.MaxTLSCertDuration())
|
||||
default:
|
||||
assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -274,8 +274,8 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
}
|
||||
|
||||
// Check for the sign ssh method, default to sign X.509
|
||||
if m := MethodFromContext(ctx); m == SignSSHMethod {
|
||||
if p.claimer.IsSSHCAEnabled() == false {
|
||||
if MethodFromContext(ctx) == SignSSHMethod {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID())
|
||||
}
|
||||
return p.authorizeSSHSign(payload)
|
||||
|
@ -296,10 +296,12 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
}
|
||||
|
||||
return append(so,
|
||||
// modifiers / withOptions
|
||||
newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID),
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
// validators
|
||||
defaultPublicKeyValidator{},
|
||||
commonNameValidator(payload.Claims.Subject),
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID),
|
||||
newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()),
|
||||
), nil
|
||||
}
|
||||
|
@ -466,13 +468,15 @@ func (p *AWS) authorizeSSHSign(claims *awsPayload) ([]SignOption, error) {
|
|||
signOptions = append(signOptions, sshCertificateDefaultsModifier(defaults))
|
||||
|
||||
return append(signOptions,
|
||||
// set the default extensions
|
||||
// Set the default extensions.
|
||||
&sshDefaultExtensionModifier{},
|
||||
// checks the validity bounds, and set the validity if has not been set
|
||||
&sshCertificateValidityModifier{p.claimer},
|
||||
// validate public key
|
||||
// Set the validity bounds if not set.
|
||||
sshDefaultValidityModifier(p.claimer),
|
||||
// Validate public key
|
||||
&sshDefaultPublicKeyValidator{},
|
||||
// require all the fields in the SSH certificate
|
||||
// Validate the validity period.
|
||||
&sshCertificateValidityValidator{p.claimer},
|
||||
// Require all the fields in the SSH certificate
|
||||
&sshCertificateDefaultValidator{},
|
||||
), nil
|
||||
}
|
||||
|
|
|
@ -266,8 +266,8 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption,
|
|||
}
|
||||
|
||||
// Check for the sign ssh method, default to sign X.509
|
||||
if m := MethodFromContext(ctx); m == SignSSHMethod {
|
||||
if p.claimer.IsSSHCAEnabled() == false {
|
||||
if MethodFromContext(ctx) == SignSSHMethod {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID())
|
||||
}
|
||||
return p.authorizeSSHSign(claims, name)
|
||||
|
@ -284,9 +284,11 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption,
|
|||
}
|
||||
|
||||
return append(so,
|
||||
defaultPublicKeyValidator{},
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
// modifiers / withOptions
|
||||
newProvisionerExtensionOption(TypeAzure, p.Name, p.TenantID),
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
// validators
|
||||
defaultPublicKeyValidator{},
|
||||
newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()),
|
||||
), nil
|
||||
}
|
||||
|
@ -323,13 +325,15 @@ func (p *Azure) authorizeSSHSign(claims azurePayload, name string) ([]SignOption
|
|||
signOptions = append(signOptions, sshCertificateDefaultsModifier(defaults))
|
||||
|
||||
return append(signOptions,
|
||||
// set the default extensions
|
||||
// Set the default extensions.
|
||||
&sshDefaultExtensionModifier{},
|
||||
// checks the validity bounds, and set the validity if has not been set
|
||||
&sshCertificateValidityModifier{p.claimer},
|
||||
// validate public key
|
||||
// Set the validity bounds if not set.
|
||||
sshDefaultValidityModifier(p.claimer),
|
||||
// Validate public key
|
||||
&sshDefaultPublicKeyValidator{},
|
||||
// require all the fields in the SSH certificate
|
||||
// Validate the validity period.
|
||||
&sshCertificateValidityValidator{p.claimer},
|
||||
// Require all the fields in the SSH certificate
|
||||
&sshCertificateDefaultValidator{},
|
||||
), nil
|
||||
}
|
||||
|
|
|
@ -129,6 +129,8 @@ func (c *Collection) LoadByCertificate(cert *x509.Certificate) (Interface, bool)
|
|||
return c.Load("gcp/" + string(provisioner.Name))
|
||||
case TypeACME:
|
||||
return c.Load("acme/" + string(provisioner.Name))
|
||||
case TypeX5C:
|
||||
return c.Load("x5c/" + string(provisioner.Name))
|
||||
default:
|
||||
return c.Load(string(provisioner.CredentialID))
|
||||
}
|
||||
|
|
|
@ -213,8 +213,8 @@ func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
}
|
||||
|
||||
// Check for the sign ssh method, default to sign X.509
|
||||
if m := MethodFromContext(ctx); m == SignSSHMethod {
|
||||
if p.claimer.IsSSHCAEnabled() == false {
|
||||
if MethodFromContext(ctx) == SignSSHMethod {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID())
|
||||
}
|
||||
return p.authorizeSSHSign(claims)
|
||||
|
@ -237,9 +237,11 @@ func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
}
|
||||
|
||||
return append(so,
|
||||
defaultPublicKeyValidator{},
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
// modifiers / withOptions
|
||||
newProvisionerExtensionOption(TypeGCP, p.Name, claims.Subject, "InstanceID", ce.InstanceID, "InstanceName", ce.InstanceName),
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
// validators
|
||||
defaultPublicKeyValidator{},
|
||||
newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()),
|
||||
), nil
|
||||
}
|
||||
|
@ -378,13 +380,15 @@ func (p *GCP) authorizeSSHSign(claims *gcpPayload) ([]SignOption, error) {
|
|||
signOptions = append(signOptions, sshCertificateDefaultsModifier(defaults))
|
||||
|
||||
return append(signOptions,
|
||||
// set the default extensions
|
||||
// Set the default extensions
|
||||
&sshDefaultExtensionModifier{},
|
||||
// checks the validity bounds, and set the validity if has not been set
|
||||
&sshCertificateValidityModifier{p.claimer},
|
||||
// validate public key
|
||||
// Set the validity bounds if not set.
|
||||
sshDefaultValidityModifier(p.claimer),
|
||||
// Validate public key
|
||||
&sshDefaultPublicKeyValidator{},
|
||||
// require all the fields in the SSH certificate
|
||||
// Validate the validity period.
|
||||
&sshCertificateValidityValidator{p.claimer},
|
||||
// Require all the fields in the SSH certificate
|
||||
&sshCertificateDefaultValidator{},
|
||||
), nil
|
||||
}
|
||||
|
|
|
@ -141,9 +141,9 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Check for SSH token
|
||||
if claims.Step != nil && claims.Step.SSH != nil {
|
||||
if p.claimer.IsSSHCAEnabled() == false {
|
||||
// Check for SSH sign-ing request.
|
||||
if MethodFromContext(ctx) == SignSSHMethod {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID())
|
||||
}
|
||||
return p.authorizeSSHSign(claims)
|
||||
|
@ -158,13 +158,15 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
|
||||
dnsNames, ips, emails := x509util.SplitSANs(claims.SANs)
|
||||
return []SignOption{
|
||||
defaultPublicKeyValidator{},
|
||||
commonNameValidator(claims.Subject),
|
||||
dnsNamesValidator(dnsNames),
|
||||
ipAddressesValidator(ips),
|
||||
emailAddressesValidator(emails),
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
// modifiers / withOptions
|
||||
newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID),
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
// validators
|
||||
commonNameValidator(claims.Subject),
|
||||
defaultPublicKeyValidator{},
|
||||
dnsNamesValidator(dnsNames),
|
||||
emailAddressesValidator(emails),
|
||||
ipAddressesValidator(ips),
|
||||
newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()),
|
||||
}, nil
|
||||
}
|
||||
|
@ -180,6 +182,9 @@ func (p *JWK) AuthorizeRenewal(cert *x509.Certificate) error {
|
|||
// authorizeSSHSign returns the list of SignOption for a SignSSH request.
|
||||
func (p *JWK) authorizeSSHSign(claims *jwtPayload) ([]SignOption, error) {
|
||||
t := now()
|
||||
if claims.Step == nil || claims.Step.SSH == nil {
|
||||
return nil, errors.New("authorization token must be an SSH provisioning token")
|
||||
}
|
||||
opts := claims.Step.SSH
|
||||
signOptions := []SignOption{
|
||||
// validates user's SSHOptions with the ones in the token
|
||||
|
@ -206,13 +211,15 @@ func (p *JWK) authorizeSSHSign(claims *jwtPayload) ([]SignOption, error) {
|
|||
signOptions = append(signOptions, sshCertificateDefaultsModifier{CertType: SSHUserCert})
|
||||
|
||||
return append(signOptions,
|
||||
// set the default extensions
|
||||
// Set the default extensions.
|
||||
&sshDefaultExtensionModifier{},
|
||||
// checks the validity bounds, and set the validity if has not been set
|
||||
&sshCertificateValidityModifier{p.claimer},
|
||||
// validate public key
|
||||
// Set the validity bounds if not set.
|
||||
sshDefaultValidityModifier(p.claimer),
|
||||
// Validate public key
|
||||
&sshDefaultPublicKeyValidator{},
|
||||
// require all the fields in the SSH certificate
|
||||
// Validate the validity period.
|
||||
&sshCertificateValidityValidator{p.claimer},
|
||||
// Require and validate all the default fields in the SSH certificate.
|
||||
&sshCertificateDefaultValidator{},
|
||||
), nil
|
||||
}
|
||||
|
|
|
@ -6,33 +6,16 @@ import (
|
|||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/cli/jose"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultDisableRenewal = false
|
||||
defaultEnableSSHCA = true
|
||||
globalProvisionerClaims = Claims{
|
||||
MinTLSDur: &Duration{5 * time.Minute},
|
||||
MaxTLSDur: &Duration{24 * time.Hour},
|
||||
DefaultTLSDur: &Duration{24 * time.Hour},
|
||||
DisableRenewal: &defaultDisableRenewal,
|
||||
MinUserSSHDur: &Duration{Duration: 5 * time.Minute}, // User SSH certs
|
||||
MaxUserSSHDur: &Duration{Duration: 24 * time.Hour},
|
||||
DefaultUserSSHDur: &Duration{Duration: 4 * time.Hour},
|
||||
MinHostSSHDur: &Duration{Duration: 5 * time.Minute}, // Host SSH certs
|
||||
MaxHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour},
|
||||
DefaultHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour},
|
||||
EnableSSHCA: &defaultEnableSSHCA,
|
||||
}
|
||||
)
|
||||
|
||||
func TestJWK_Getters(t *testing.T) {
|
||||
p, err := generateJWK()
|
||||
assert.FatalError(t, err)
|
||||
|
@ -247,7 +230,7 @@ func TestJWK_AuthorizeSign(t *testing.T) {
|
|||
key1, err := decryptJSONWebKey(p1.EncryptedKey)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
t1, err := generateSimpleToken(p1.Name, testAudiences.Sign[0], key1)
|
||||
t1, err := generateToken("subject", p1.Name, testAudiences.Sign[0], "name@smallstep.com", []string{"127.0.0.1", "max@smallstep.com", "foo"}, time.Now(), key1)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
t2, err := generateToken("subject", p1.Name, testAudiences.Sign[0], "name@smallstep.com", []string{}, time.Now(), key1)
|
||||
|
@ -264,10 +247,13 @@ func TestJWK_AuthorizeSign(t *testing.T) {
|
|||
prov *JWK
|
||||
args args
|
||||
err error
|
||||
dns []string
|
||||
emails []string
|
||||
ips []net.IP
|
||||
}{
|
||||
{"fail-signature", p1, args{failSig}, errors.New("error parsing claims: square/go-jose: error in cryptographic primitive")},
|
||||
{"ok-sans", p1, args{t1}, nil},
|
||||
{"ok-no-sans", p1, args{t2}, nil},
|
||||
{name: "fail-signature", prov: p1, args: args{failSig}, err: errors.New("error parsing claims: square/go-jose: error in cryptographic primitive")},
|
||||
{"ok-sans", p1, args{t1}, nil, []string{"foo"}, []string{"max@smallstep.com"}, []net.IP{net.ParseIP("127.0.0.1")}},
|
||||
{"ok-no-sans", p1, args{t2}, nil, []string{"subject"}, []string{}, []net.IP{}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -279,19 +265,30 @@ func TestJWK_AuthorizeSign(t *testing.T) {
|
|||
} else {
|
||||
if assert.NotNil(t, got) {
|
||||
assert.Len(t, 8, got)
|
||||
|
||||
_cnv := got[1]
|
||||
cnv, ok := _cnv.(commonNameValidator)
|
||||
assert.True(t, ok)
|
||||
assert.Equals(t, string(cnv), "subject")
|
||||
|
||||
_dnv := got[2]
|
||||
dnv, ok := _dnv.(dnsNamesValidator)
|
||||
assert.True(t, ok)
|
||||
if tt.name == "ok-sans" {
|
||||
assert.Equals(t, []string(dnv), []string{"test.smallstep.com"})
|
||||
} else {
|
||||
assert.Equals(t, []string(dnv), []string{"subject"})
|
||||
for _, o := range got {
|
||||
switch v := o.(type) {
|
||||
case *provisionerExtensionOption:
|
||||
assert.Equals(t, v.Type, int(TypeJWK))
|
||||
assert.Equals(t, v.Name, tt.prov.GetName())
|
||||
assert.Equals(t, v.CredentialID, tt.prov.Key.KeyID)
|
||||
assert.Len(t, 0, v.KeyValuePairs)
|
||||
case profileDefaultDuration:
|
||||
assert.Equals(t, time.Duration(v), tt.prov.claimer.DefaultTLSCertDuration())
|
||||
case commonNameValidator:
|
||||
assert.Equals(t, string(v), "subject")
|
||||
case defaultPublicKeyValidator:
|
||||
case dnsNamesValidator:
|
||||
assert.Equals(t, []string(v), tt.dns)
|
||||
case emailAddressesValidator:
|
||||
assert.Equals(t, []string(v), tt.emails)
|
||||
case ipAddressesValidator:
|
||||
assert.Equals(t, []net.IP(v), tt.ips)
|
||||
case *validityValidator:
|
||||
assert.Equals(t, v.min, tt.prov.claimer.MinTLSCertDuration())
|
||||
assert.Equals(t, v.max, tt.prov.claimer.MaxTLSCertDuration())
|
||||
default:
|
||||
assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -285,26 +285,19 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e
|
|||
}
|
||||
|
||||
// Check for the sign ssh method, default to sign X.509
|
||||
if m := MethodFromContext(ctx); m == SignSSHMethod {
|
||||
if o.claimer.IsSSHCAEnabled() == false {
|
||||
if MethodFromContext(ctx) == SignSSHMethod {
|
||||
if !o.claimer.IsSSHCAEnabled() {
|
||||
return nil, errors.Errorf("ssh ca is disabled for provisioner %s", o.GetID())
|
||||
}
|
||||
return o.authorizeSSHSign(claims)
|
||||
}
|
||||
|
||||
// Admins should be able to authorize any SAN
|
||||
if o.IsAdmin(claims.Email) {
|
||||
return []SignOption{
|
||||
profileDefaultDuration(o.claimer.DefaultTLSCertDuration()),
|
||||
newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID),
|
||||
newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
so := []SignOption{
|
||||
defaultPublicKeyValidator{},
|
||||
profileDefaultDuration(o.claimer.DefaultTLSCertDuration()),
|
||||
// modifiers / withOptions
|
||||
newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID),
|
||||
profileDefaultDuration(o.claimer.DefaultTLSCertDuration()),
|
||||
// validators
|
||||
defaultPublicKeyValidator{},
|
||||
newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()),
|
||||
}
|
||||
// Admins should be able to authorize any SAN
|
||||
|
@ -350,13 +343,15 @@ func (o *OIDC) authorizeSSHSign(claims *openIDPayload) ([]SignOption, error) {
|
|||
signOptions = append(signOptions, sshCertificateDefaultsModifier(defaults))
|
||||
|
||||
return append(signOptions,
|
||||
// set the default extensions
|
||||
// Set the default extensions
|
||||
&sshDefaultExtensionModifier{},
|
||||
// checks the validity bounds, and set the validity if has not been set
|
||||
&sshCertificateValidityModifier{o.claimer},
|
||||
// validate public key
|
||||
// Set the validity bounds if not set.
|
||||
sshDefaultValidityModifier(o.claimer),
|
||||
// Validate public key
|
||||
&sshDefaultPublicKeyValidator{},
|
||||
// require all the fields in the SSH certificate
|
||||
// Validate the validity period.
|
||||
&sshCertificateValidityValidator{o.claimer},
|
||||
// Require all the fields in the SSH certificate
|
||||
&sshCertificateDefaultValidator{},
|
||||
), nil
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/cli/jose"
|
||||
)
|
||||
|
@ -298,12 +299,32 @@ func TestOIDC_AuthorizeSign(t *testing.T) {
|
|||
if err != nil {
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NotNil(t, got)
|
||||
if assert.NotNil(t, got) {
|
||||
if tt.name == "admin" {
|
||||
assert.Len(t, 3, got)
|
||||
assert.Len(t, 4, got)
|
||||
} else {
|
||||
assert.Len(t, 5, got)
|
||||
}
|
||||
for _, o := range got {
|
||||
switch v := o.(type) {
|
||||
case *provisionerExtensionOption:
|
||||
assert.Equals(t, v.Type, int(TypeOIDC))
|
||||
assert.Equals(t, v.Name, tt.prov.GetName())
|
||||
assert.Equals(t, v.CredentialID, tt.prov.ClientID)
|
||||
assert.Len(t, 0, v.KeyValuePairs)
|
||||
case profileDefaultDuration:
|
||||
assert.Equals(t, time.Duration(v), tt.prov.claimer.DefaultTLSCertDuration())
|
||||
case defaultPublicKeyValidator:
|
||||
case *validityValidator:
|
||||
assert.Equals(t, v.min, tt.prov.claimer.MinTLSCertDuration())
|
||||
assert.Equals(t, v.max, tt.prov.claimer.MaxTLSCertDuration())
|
||||
case emailOnlyIdentity:
|
||||
assert.Equals(t, string(v), "name@smallstep.com")
|
||||
default:
|
||||
assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -86,6 +86,8 @@ const (
|
|||
TypeAzure Type = 5
|
||||
// TypeACME is used to indicate the ACME provisioners.
|
||||
TypeACME Type = 6
|
||||
// TypeX5C is used to indicate the X5C provisioners.
|
||||
TypeX5C Type = 7
|
||||
|
||||
// RevokeAudienceKey is the key for the 'revoke' audiences in the audiences map.
|
||||
RevokeAudienceKey = "revoke"
|
||||
|
@ -108,6 +110,8 @@ func (t Type) String() string {
|
|||
return "Azure"
|
||||
case TypeACME:
|
||||
return "ACME"
|
||||
case TypeX5C:
|
||||
return "X5C"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
@ -157,6 +161,8 @@ func (l *List) UnmarshalJSON(data []byte) error {
|
|||
p = &Azure{}
|
||||
case "acme":
|
||||
p = &ACME{}
|
||||
case "x5c":
|
||||
p = &X5C{}
|
||||
default:
|
||||
// Skip unsupported provisioners. A client using this method may be
|
||||
// compiled with a version of smallstep/certificates that does not
|
||||
|
|
|
@ -53,19 +53,6 @@ func (v profileWithOption) Option(Options) x509util.WithOption {
|
|||
return x509util.WithOption(v)
|
||||
}
|
||||
|
||||
// profileDefaultDuration is a wrapper against x509util.WithOption to conform the
|
||||
// interface.
|
||||
type profileDefaultDuration time.Duration
|
||||
|
||||
func (v profileDefaultDuration) Option(so Options) x509util.WithOption {
|
||||
notBefore := so.NotBefore.Time()
|
||||
if notBefore.IsZero() {
|
||||
notBefore = time.Now()
|
||||
}
|
||||
notAfter := so.NotAfter.RelativeTime(notBefore)
|
||||
return x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(v))
|
||||
}
|
||||
|
||||
// emailOnlyIdentity is a CertificateRequestValidator that checks that the only
|
||||
// SAN provided is the given email address.
|
||||
type emailOnlyIdentity string
|
||||
|
@ -197,7 +184,61 @@ func (v emailAddressesValidator) Valid(req *x509.CertificateRequest) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// validityValidator validates the certificate temporal validity settings.
|
||||
// profileDefaultDuration is a wrapper against x509util.WithOption to conform
|
||||
// the SignOption interface.
|
||||
type profileDefaultDuration time.Duration
|
||||
|
||||
func (v profileDefaultDuration) Option(so Options) x509util.WithOption {
|
||||
notBefore := so.NotBefore.Time()
|
||||
if notBefore.IsZero() {
|
||||
notBefore = time.Now()
|
||||
}
|
||||
notAfter := so.NotAfter.RelativeTime(notBefore)
|
||||
return x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(v))
|
||||
}
|
||||
|
||||
// profileLimitDuration is an x509 profile option that modifies an x509 validity
|
||||
// period according to an imposed expiration time.
|
||||
type profileLimitDuration struct {
|
||||
def time.Duration
|
||||
notAfter time.Time
|
||||
}
|
||||
|
||||
// Option returns an x509util option that limits the validity period of a
|
||||
// certificate to one that is superficially imposed.
|
||||
func (v profileLimitDuration) Option(so Options) x509util.WithOption {
|
||||
return func(p x509util.Profile) error {
|
||||
n := now()
|
||||
notBefore := so.NotBefore.Time()
|
||||
if notBefore.IsZero() {
|
||||
notBefore = n
|
||||
}
|
||||
if notBefore.After(v.notAfter) {
|
||||
return errors.Errorf("provisioning credential expiration (%s) is before "+
|
||||
"requested certificate notBefore (%s)", v.notAfter, notBefore)
|
||||
}
|
||||
|
||||
notAfter := so.NotAfter.RelativeTime(notBefore)
|
||||
if notAfter.After(v.notAfter) {
|
||||
return errors.Errorf("provisioning credential expiration (%s) is before "+
|
||||
"requested certificate notAfter (%s)", v.notAfter, notBefore)
|
||||
}
|
||||
if notAfter.IsZero() {
|
||||
t := notBefore.Add(v.def)
|
||||
if t.After(v.notAfter) {
|
||||
notAfter = v.notAfter
|
||||
} else {
|
||||
notAfter = t
|
||||
}
|
||||
}
|
||||
crt := p.Subject()
|
||||
crt.NotBefore = notBefore
|
||||
crt.NotAfter = notAfter
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// validityValidator validates the certificate validity settings.
|
||||
type validityValidator struct {
|
||||
min time.Duration
|
||||
max time.Duration
|
||||
|
@ -208,7 +249,8 @@ func newValidityValidator(min, max time.Duration) *validityValidator {
|
|||
return &validityValidator{min: min, max: max}
|
||||
}
|
||||
|
||||
// Validate validates the certificate temporal validity settings.
|
||||
// Valid validates the certificate validity settings (notBefore/notAfter) and
|
||||
// and total duration.
|
||||
func (v *validityValidator) Valid(crt *x509.Certificate) error {
|
||||
var (
|
||||
na = crt.NotAfter
|
||||
|
|
|
@ -273,3 +273,87 @@ func Test_validityValidator_Valid(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_profileLimitDuration_Option(t *testing.T) {
|
||||
n := now()
|
||||
type test struct {
|
||||
pld profileLimitDuration
|
||||
so Options
|
||||
cert *x509.Certificate
|
||||
valid func(*x509.Certificate)
|
||||
err error
|
||||
}
|
||||
tests := map[string]func() test{
|
||||
"fail/notBefore-after-limit": func() test {
|
||||
d, err := ParseTimeDuration("8h")
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)},
|
||||
so: Options{NotBefore: d},
|
||||
cert: new(x509.Certificate),
|
||||
err: errors.New("provisioning credential expiration ("),
|
||||
}
|
||||
},
|
||||
"fail/requested-notAfter-after-limit": func() test {
|
||||
d, err := ParseTimeDuration("4h")
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)},
|
||||
so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), NotAfter: d},
|
||||
cert: new(x509.Certificate),
|
||||
err: errors.New("provisioning credential expiration ("),
|
||||
}
|
||||
},
|
||||
"ok/valid-notAfter-requested": func() test {
|
||||
d, err := ParseTimeDuration("2h")
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)},
|
||||
so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), NotAfter: d},
|
||||
cert: new(x509.Certificate),
|
||||
valid: func(cert *x509.Certificate) {
|
||||
assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour))
|
||||
assert.Equals(t, cert.NotAfter, n.Add(5*time.Hour))
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/valid-notAfter-nil-limit-over-default": func() test {
|
||||
return test{
|
||||
pld: profileLimitDuration{def: 1 * time.Hour, notAfter: n.Add(6 * time.Hour)},
|
||||
so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour))},
|
||||
cert: new(x509.Certificate),
|
||||
valid: func(cert *x509.Certificate) {
|
||||
assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour))
|
||||
assert.Equals(t, cert.NotAfter, n.Add(4*time.Hour))
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/valid-notAfter-nil-limit-under-default": func() test {
|
||||
return test{
|
||||
pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)},
|
||||
so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour))},
|
||||
cert: new(x509.Certificate),
|
||||
valid: func(cert *x509.Certificate) {
|
||||
assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour))
|
||||
assert.Equals(t, cert.NotAfter, n.Add(6*time.Hour))
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, run := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tt := run()
|
||||
prof := &x509util.Leaf{}
|
||||
prof.SetSubject(tt.cert)
|
||||
if err := tt.pld.Option(tt.so)(prof); err != nil {
|
||||
if assert.NotNil(t, tt.err) {
|
||||
assert.HasPrefix(t, err.Error(), tt.err.Error())
|
||||
}
|
||||
} else {
|
||||
if assert.Nil(t, tt.err) {
|
||||
tt.valid(prof.Subject())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,47 +191,67 @@ func (m *sshDefaultExtensionModifier) Modify(cert *ssh.Certificate) error {
|
|||
}
|
||||
}
|
||||
|
||||
// sshCertificateValidityModifier is a SSHCertificateModifier checks the
|
||||
// sshValidityModifier is an SSHCertificateModifier that checks the
|
||||
// validity bounds, setting them if they are not provided. It will fail if a
|
||||
// CertType has not been set or is not valid.
|
||||
type sshCertificateValidityModifier struct {
|
||||
type sshValidityModifier struct {
|
||||
*Claimer
|
||||
validBefore time.Time
|
||||
}
|
||||
|
||||
func (m *sshCertificateValidityModifier) Modify(cert *ssh.Certificate) error {
|
||||
var d, min, max time.Duration
|
||||
func (m *sshValidityModifier) Modify(cert *ssh.Certificate) error {
|
||||
var d time.Duration
|
||||
|
||||
switch cert.CertType {
|
||||
case ssh.UserCert:
|
||||
d = m.DefaultUserSSHCertDuration()
|
||||
min = m.MinUserSSHCertDuration()
|
||||
max = m.MaxUserSSHCertDuration()
|
||||
case ssh.HostCert:
|
||||
d = m.DefaultHostSSHCertDuration()
|
||||
min = m.MinHostSSHCertDuration()
|
||||
max = m.MaxHostSSHCertDuration()
|
||||
case 0:
|
||||
return errors.New("ssh certificate type has not been set")
|
||||
default:
|
||||
return errors.Errorf("unknown ssh certificate type %d", cert.CertType)
|
||||
}
|
||||
|
||||
hasLimit := !m.validBefore.IsZero()
|
||||
|
||||
n := now()
|
||||
if cert.ValidAfter == 0 {
|
||||
cert.ValidAfter = uint64(now().Truncate(time.Second).Unix())
|
||||
cert.ValidAfter = uint64(n.Truncate(time.Second).Unix())
|
||||
}
|
||||
if cert.ValidBefore == 0 {
|
||||
t := time.Unix(int64(cert.ValidAfter), 0)
|
||||
cert.ValidBefore = uint64(t.Add(d).Unix())
|
||||
certValidAfter := time.Unix(int64(cert.ValidAfter), 0)
|
||||
if hasLimit && certValidAfter.After(m.validBefore) {
|
||||
return errors.Errorf("provisioning credential expiration (%s) is before "+
|
||||
"requested certificate validAfter (%s)", m.validBefore, certValidAfter)
|
||||
}
|
||||
|
||||
diff := time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second
|
||||
switch {
|
||||
case diff < min:
|
||||
return errors.Errorf("ssh certificate duration cannot be lower than %s", min)
|
||||
case diff > max:
|
||||
return errors.Errorf("ssh certificate duration cannot be greater than %s", max)
|
||||
default:
|
||||
return nil
|
||||
if cert.ValidBefore == 0 {
|
||||
certValidBefore := certValidAfter.Add(d)
|
||||
if hasLimit && m.validBefore.Before(certValidBefore) {
|
||||
certValidBefore = m.validBefore
|
||||
}
|
||||
cert.ValidBefore = uint64(certValidBefore.Unix())
|
||||
} else if hasLimit {
|
||||
certValidBefore := time.Unix(int64(cert.ValidBefore), 0)
|
||||
if m.validBefore.Before(certValidBefore) {
|
||||
return errors.Errorf("provisioning credential expiration (%s) is before "+
|
||||
"requested certificate validBefore (%s)", m.validBefore, certValidBefore)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sshDefaultValidityModifier(c *Claimer) SSHCertificateModifier {
|
||||
return &sshValidityModifier{c, time.Time{}}
|
||||
}
|
||||
|
||||
// sshLimitValidityModifier adjusts the duration to
|
||||
// min(default, remaining provisioning credential duration).
|
||||
// E.g. if the default is 12hrs but the remaining validity of the provisioning
|
||||
// credential is only 4hrs, this option will set the value to 4hrs (the min of the two values).
|
||||
func sshLimitValidityModifier(c *Claimer, validBefore time.Time) SSHCertificateModifier {
|
||||
return &sshValidityModifier{c, validBefore}
|
||||
}
|
||||
|
||||
// sshCertificateOptionsValidator validates the user SSHOptions with the ones
|
||||
|
@ -245,6 +265,48 @@ func (v sshCertificateOptionsValidator) Valid(got SSHOptions) error {
|
|||
return want.match(got)
|
||||
}
|
||||
|
||||
type sshCertificateValidityValidator struct {
|
||||
*Claimer
|
||||
}
|
||||
|
||||
func (v *sshCertificateValidityValidator) Valid(cert *ssh.Certificate) error {
|
||||
switch {
|
||||
case cert.ValidAfter == 0:
|
||||
return errors.New("ssh certificate validAfter cannot be 0")
|
||||
case cert.ValidBefore < uint64(now().Unix()):
|
||||
return errors.New("ssh certificate validBefore cannot be in the past")
|
||||
case cert.ValidBefore < cert.ValidAfter:
|
||||
return errors.New("ssh certificate validBefore cannot be before validAfter")
|
||||
}
|
||||
|
||||
var min, max time.Duration
|
||||
switch cert.CertType {
|
||||
case ssh.UserCert:
|
||||
min = v.MinUserSSHCertDuration()
|
||||
max = v.MaxUserSSHCertDuration()
|
||||
case ssh.HostCert:
|
||||
min = v.MinHostSSHCertDuration()
|
||||
max = v.MaxHostSSHCertDuration()
|
||||
case 0:
|
||||
return errors.New("ssh certificate type has not been set")
|
||||
default:
|
||||
return errors.Errorf("unknown ssh certificate type %d", cert.CertType)
|
||||
}
|
||||
|
||||
// seconds
|
||||
dur := time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second
|
||||
switch {
|
||||
case dur < min:
|
||||
return errors.Errorf("requested duration of %s is less than minimum "+
|
||||
"accepted duration for selected provisioner of %s", dur, min)
|
||||
case dur > max:
|
||||
return errors.Errorf("requested duration of %s is greater than maximum "+
|
||||
"accepted duration for selected provisioner of %s", dur, max)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// sshCertificateDefaultValidator implements a simple validator for all the
|
||||
// fields in the SSH certificate.
|
||||
type sshCertificateDefaultValidator struct{}
|
||||
|
|
|
@ -190,3 +190,197 @@ func Test_sshCertificateDefaultValidator_Valid(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sshCertificateValidityValidator(t *testing.T) {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
v := sshCertificateValidityValidator{p.claimer}
|
||||
n := now()
|
||||
tests := []struct {
|
||||
name string
|
||||
cert *ssh.Certificate
|
||||
err error
|
||||
}{
|
||||
{
|
||||
"fail/validAfter-0",
|
||||
&ssh.Certificate{CertType: ssh.UserCert},
|
||||
errors.New("ssh certificate validAfter cannot be 0"),
|
||||
},
|
||||
{
|
||||
"fail/validBefore-in-past",
|
||||
&ssh.Certificate{CertType: ssh.UserCert, ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(-time.Minute).Unix())},
|
||||
errors.New("ssh certificate validBefore cannot be in the past"),
|
||||
},
|
||||
{
|
||||
"fail/validBefore-before-validAfter",
|
||||
&ssh.Certificate{CertType: ssh.UserCert, ValidAfter: uint64(now().Add(5 * time.Minute).Unix()), ValidBefore: uint64(now().Add(3 * time.Minute).Unix())},
|
||||
errors.New("ssh certificate validBefore cannot be before validAfter"),
|
||||
},
|
||||
{
|
||||
"fail/cert-type-not-set",
|
||||
&ssh.Certificate{ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(10 * time.Minute).Unix())},
|
||||
errors.New("ssh certificate type has not been set"),
|
||||
},
|
||||
{
|
||||
"fail/unexpected-cert-type",
|
||||
&ssh.Certificate{
|
||||
CertType: 3,
|
||||
ValidAfter: uint64(now().Unix()),
|
||||
ValidBefore: uint64(now().Add(10 * time.Minute).Unix()),
|
||||
},
|
||||
errors.New("unknown ssh certificate type 3"),
|
||||
},
|
||||
{
|
||||
"fail/duration<min",
|
||||
&ssh.Certificate{
|
||||
CertType: 1,
|
||||
ValidAfter: uint64(n.Unix()),
|
||||
ValidBefore: uint64(n.Add(4 * time.Minute).Unix()),
|
||||
},
|
||||
errors.New("requested duration of 4m0s is less than minimum accepted duration for selected provisioner of 5m0s"),
|
||||
},
|
||||
{
|
||||
"fail/duration>max",
|
||||
&ssh.Certificate{
|
||||
CertType: 1,
|
||||
ValidAfter: uint64(n.Unix()),
|
||||
ValidBefore: uint64(n.Add(48 * time.Hour).Unix()),
|
||||
},
|
||||
errors.New("requested duration of 48h0m0s is greater than maximum accepted duration for selected provisioner of 24h0m0s"),
|
||||
},
|
||||
{
|
||||
"ok",
|
||||
&ssh.Certificate{
|
||||
CertType: 1,
|
||||
ValidAfter: uint64(now().Unix()),
|
||||
ValidBefore: uint64(now().Add(8 * time.Hour).Unix()),
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := v.Valid(tt.cert); err != nil {
|
||||
if assert.NotNil(t, tt.err) {
|
||||
assert.HasPrefix(t, err.Error(), tt.err.Error())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, tt.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sshValidityModifier(t *testing.T) {
|
||||
n := now()
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
type test struct {
|
||||
svm *sshValidityModifier
|
||||
cert *ssh.Certificate
|
||||
valid func(*ssh.Certificate)
|
||||
err error
|
||||
}
|
||||
tests := map[string]func() test{
|
||||
"fail/type-not-set": func() test {
|
||||
return test{
|
||||
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(6 * time.Hour)},
|
||||
cert: &ssh.Certificate{
|
||||
ValidAfter: uint64(n.Unix()),
|
||||
ValidBefore: uint64(n.Add(8 * time.Hour).Unix()),
|
||||
},
|
||||
err: errors.New("ssh certificate type has not been set"),
|
||||
}
|
||||
},
|
||||
"fail/type-not-recognized": func() test {
|
||||
return test{
|
||||
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(6 * time.Hour)},
|
||||
cert: &ssh.Certificate{
|
||||
CertType: 4,
|
||||
ValidAfter: uint64(n.Unix()),
|
||||
ValidBefore: uint64(n.Add(8 * time.Hour).Unix()),
|
||||
},
|
||||
err: errors.New("unknown ssh certificate type 4"),
|
||||
}
|
||||
},
|
||||
"fail/requested-validAfter-after-limit": func() test {
|
||||
return test{
|
||||
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(1 * time.Hour)},
|
||||
cert: &ssh.Certificate{
|
||||
CertType: 1,
|
||||
ValidAfter: uint64(n.Add(2 * time.Hour).Unix()),
|
||||
ValidBefore: uint64(n.Add(8 * time.Hour).Unix()),
|
||||
},
|
||||
err: errors.Errorf("provisioning credential expiration ("),
|
||||
}
|
||||
},
|
||||
"fail/requested-validBefore-after-limit": func() test {
|
||||
return test{
|
||||
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(1 * time.Hour)},
|
||||
cert: &ssh.Certificate{
|
||||
CertType: 1,
|
||||
ValidAfter: uint64(n.Unix()),
|
||||
ValidBefore: uint64(n.Add(2 * time.Hour).Unix()),
|
||||
},
|
||||
err: errors.New("provisioning credential expiration ("),
|
||||
}
|
||||
},
|
||||
"ok/valid-requested-validBefore": func() test {
|
||||
va, vb := uint64(n.Unix()), uint64(n.Add(2*time.Hour).Unix())
|
||||
return test{
|
||||
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(3 * time.Hour)},
|
||||
cert: &ssh.Certificate{
|
||||
CertType: 1,
|
||||
ValidAfter: va,
|
||||
ValidBefore: vb,
|
||||
},
|
||||
valid: func(cert *ssh.Certificate) {
|
||||
assert.Equals(t, cert.ValidAfter, va)
|
||||
assert.Equals(t, cert.ValidBefore, vb)
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/empty-requested-validBefore-limit-after-default": func() test {
|
||||
va := uint64(n.Unix())
|
||||
return test{
|
||||
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(5 * time.Hour)},
|
||||
cert: &ssh.Certificate{
|
||||
CertType: 1,
|
||||
ValidAfter: va,
|
||||
},
|
||||
valid: func(cert *ssh.Certificate) {
|
||||
assert.Equals(t, cert.ValidAfter, va)
|
||||
assert.Equals(t, cert.ValidBefore, uint64(n.Add(4*time.Hour).Unix()))
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/empty-requested-validBefore-limit-before-default": func() test {
|
||||
va := uint64(n.Unix())
|
||||
return test{
|
||||
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(3 * time.Hour)},
|
||||
cert: &ssh.Certificate{
|
||||
CertType: 1,
|
||||
ValidAfter: va,
|
||||
},
|
||||
valid: func(cert *ssh.Certificate) {
|
||||
assert.Equals(t, cert.ValidAfter, va)
|
||||
assert.Equals(t, cert.ValidBefore, uint64(n.Add(3*time.Hour).Unix()))
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, run := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tt := run()
|
||||
if err := tt.svm.Modify(tt.cert); err != nil {
|
||||
if assert.NotNil(t, tt.err) {
|
||||
assert.HasPrefix(t, err.Error(), tt.err.Error())
|
||||
}
|
||||
} else {
|
||||
if assert.Nil(t, tt.err) {
|
||||
tt.valid(tt.cert)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
24
authority/provisioner/testdata/x5c-leaf.crt
vendored
Normal file
24
authority/provisioner/testdata/x5c-leaf.crt
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAV+gAwIBAgIQFdu723gqgGaTaqjf6ny88zAKBggqhkjOPQQDAjAcMRow
|
||||
GAYDVQQDExFpbnRlcm1lZGlhdGUtdGVzdDAgFw0xOTEwMDIwMzE4NTNaGA8yMTE5
|
||||
MDkwODAzMTg1MVowFDESMBAGA1UEAxMJbGVhZi10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEaV6807GhWEtMxA39zjuMVHAiN2/Ri5B1R1s+Y/8mlrKIvuvr
|
||||
VpgSPXYruNRFduPWX564Abz/TDmb276JbKGeQqOBiDCBhTAOBgNVHQ8BAf8EBAMC
|
||||
BaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBReMkPW
|
||||
f4MNWdg7KN4xI4ZLJd0IJDAfBgNVHSMEGDAWgBSckDGJlzLaJsdy698XH32gPDMp
|
||||
czAUBgNVHREEDTALgglsZWFmLXRlc3QwCgYIKoZIzj0EAwIDRwAwRAIgKYLKXpTN
|
||||
wtvZZaIvDzq1p8MO/SZ8yI42Ot69dNk/QtkCIBSvg5PozYcfbvwkgX5SwsjfYu0Z
|
||||
AvUgkUQ2G25NBRmX
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw
|
||||
EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw
|
||||
MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl
|
||||
qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC
|
||||
AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99
|
||||
oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw
|
||||
E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1
|
||||
2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC
|
||||
lgsqsR63is+0YQ==
|
||||
-----END CERTIFICATE-----
|
5
authority/provisioner/testdata/x5c-leaf.key
vendored
Normal file
5
authority/provisioner/testdata/x5c-leaf.key
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIALytC4LyTTAagMLMv+rzq2vtfhFkhuyBz4kqsnRs6zioAoGCCqGSM49
|
||||
AwEHoUQDQgAEaV6807GhWEtMxA39zjuMVHAiN2/Ri5B1R1s+Y/8mlrKIvuvrVpgS
|
||||
PXYruNRFduPWX564Abz/TDmb276JbKGeQg==
|
||||
-----END EC PRIVATE KEY-----
|
|
@ -19,9 +19,44 @@ import (
|
|||
"github.com/smallstep/cli/jose"
|
||||
)
|
||||
|
||||
var testAudiences = Audiences{
|
||||
var (
|
||||
defaultDisableRenewal = false
|
||||
defaultEnableSSHCA = true
|
||||
globalProvisionerClaims = Claims{
|
||||
MinTLSDur: &Duration{5 * time.Minute},
|
||||
MaxTLSDur: &Duration{24 * time.Hour},
|
||||
DefaultTLSDur: &Duration{24 * time.Hour},
|
||||
DisableRenewal: &defaultDisableRenewal,
|
||||
MinUserSSHDur: &Duration{Duration: 5 * time.Minute}, // User SSH certs
|
||||
MaxUserSSHDur: &Duration{Duration: 24 * time.Hour},
|
||||
DefaultUserSSHDur: &Duration{Duration: 4 * time.Hour},
|
||||
MinHostSSHDur: &Duration{Duration: 5 * time.Minute}, // Host SSH certs
|
||||
MaxHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour},
|
||||
DefaultHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour},
|
||||
EnableSSHCA: &defaultEnableSSHCA,
|
||||
}
|
||||
testAudiences = Audiences{
|
||||
Sign: []string{"https://ca.smallstep.com/sign", "https://ca.smallstep.com/1.0/sign"},
|
||||
Revoke: []string{"https://ca.smallstep.com/revoke", "https://ca.smallstep.com/1.0/revoke"},
|
||||
}
|
||||
)
|
||||
|
||||
func provisionerClaims() *Claims {
|
||||
ddr := false
|
||||
des := true
|
||||
return &Claims{
|
||||
MinTLSDur: &Duration{5 * time.Minute},
|
||||
MaxTLSDur: &Duration{24 * time.Hour},
|
||||
DefaultTLSDur: &Duration{24 * time.Hour},
|
||||
DisableRenewal: &ddr,
|
||||
MinUserSSHDur: &Duration{Duration: 5 * time.Minute}, // User SSH certs
|
||||
MaxUserSSHDur: &Duration{Duration: 24 * time.Hour},
|
||||
DefaultUserSSHDur: &Duration{Duration: 4 * time.Hour},
|
||||
MinHostSSHDur: &Duration{Duration: 5 * time.Minute}, // Host SSH certs
|
||||
MaxHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour},
|
||||
DefaultHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour},
|
||||
EnableSSHCA: &des,
|
||||
}
|
||||
}
|
||||
|
||||
const awsTestCertificate = `-----BEGIN CERTIFICATE-----
|
||||
|
@ -162,6 +197,58 @@ func generateJWK() (*JWK, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func generateX5C(root []byte) (*X5C, error) {
|
||||
if root == nil {
|
||||
root = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBhTCCASqgAwIBAgIRAMalM7pKi0GCdKjO6u88OyowCgYIKoZIzj0EAwIwFDES
|
||||
MBAGA1UEAxMJcm9vdC10ZXN0MCAXDTE5MTAwMjAyMzk0OFoYDzIxMTkwOTA4MDIz
|
||||
OTQ4WjAUMRIwEAYDVQQDEwlyb290LXRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMB
|
||||
BwNCAAS29QTCXUu7cx9sa9wZPpRSFq/zXaw8Ai3EIygayrBsKnX42U2atBUjcBZO
|
||||
BWL6A+PpLzU9ja867U5SYNHERS+Oo1swWTAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
|
||||
AQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowFAYD
|
||||
VR0RBA0wC4IJcm9vdC10ZXN0MAoGCCqGSM49BAMCA0kAMEYCIQC2vgqwla0u8LHH
|
||||
1MHob14qvS5o76HautbIBW7fcHzz5gIhAIx5A2+wkJYX4026kqaZCk/1sAwTxSGY
|
||||
M46l92gdOozT
|
||||
-----END CERTIFICATE-----`)
|
||||
}
|
||||
|
||||
name, err := randutil.Alphanumeric(10)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
claimer, err := NewClaimer(nil, globalProvisionerClaims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rootPool := x509.NewCertPool()
|
||||
|
||||
var (
|
||||
block *pem.Block
|
||||
rest = root
|
||||
)
|
||||
for rest != nil {
|
||||
block, rest = pem.Decode(rest)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error parsing x509 certificate from PEM block")
|
||||
}
|
||||
rootPool.AddCert(cert)
|
||||
}
|
||||
return &X5C{
|
||||
Name: name,
|
||||
Type: "X5C",
|
||||
Roots: root,
|
||||
Claims: &globalProvisionerClaims,
|
||||
audiences: testAudiences,
|
||||
claimer: claimer,
|
||||
rootPool: rootPool,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateOIDC() (*OIDC, error) {
|
||||
name, err := randutil.Alphanumeric(10)
|
||||
if err != nil {
|
||||
|
@ -446,11 +533,31 @@ func generateSimpleToken(iss, aud string, jwk *jose.JSONWebKey) (string, error)
|
|||
return generateToken("subject", iss, aud, "name@smallstep.com", []string{"test.smallstep.com"}, time.Now(), jwk)
|
||||
}
|
||||
|
||||
func generateToken(sub, iss, aud string, email string, sans []string, iat time.Time, jwk *jose.JSONWebKey) (string, error) {
|
||||
sig, err := jose.NewSigner(
|
||||
jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
|
||||
new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID),
|
||||
)
|
||||
type tokOption func(*jose.SignerOptions) error
|
||||
|
||||
func withX5CHdr(certs []*x509.Certificate) tokOption {
|
||||
return func(so *jose.SignerOptions) error {
|
||||
strs := make([]string, len(certs))
|
||||
for i, cert := range certs {
|
||||
strs[i] = base64.StdEncoding.EncodeToString(cert.Raw)
|
||||
}
|
||||
so.WithHeader("x5c", strs)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func generateToken(sub, iss, aud string, email string, sans []string, iat time.Time, jwk *jose.JSONWebKey, tokOpts ...tokOption) (string, error) {
|
||||
so := new(jose.SignerOptions)
|
||||
so.WithType("JWT")
|
||||
so.WithHeader("kid", jwk.KeyID)
|
||||
|
||||
for _, o := range tokOpts {
|
||||
if err := o(so); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, so)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -742,3 +849,24 @@ func generateACME() (*ACME, error) {
|
|||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func parseCerts(b []byte) ([]*x509.Certificate, error) {
|
||||
var (
|
||||
block *pem.Block
|
||||
rest = b
|
||||
certs = []*x509.Certificate{}
|
||||
)
|
||||
for rest != nil {
|
||||
block, rest = pem.Decode(rest)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error parsing x509 certificate from PEM block")
|
||||
}
|
||||
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
|
267
authority/provisioner/x5c.go
Normal file
267
authority/provisioner/x5c.go
Normal file
|
@ -0,0 +1,267 @@
|
|||
package provisioner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
"github.com/smallstep/cli/jose"
|
||||
)
|
||||
|
||||
// x5cPayload extends jwt.Claims with step attributes.
|
||||
type x5cPayload struct {
|
||||
jose.Claims
|
||||
SANs []string `json:"sans,omitempty"`
|
||||
Step *stepPayload `json:"step,omitempty"`
|
||||
chains [][]*x509.Certificate
|
||||
}
|
||||
|
||||
// X5C is the default provisioner, an entity that can sign tokens necessary for
|
||||
// signature requests.
|
||||
type X5C struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Roots []byte `json:"roots"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
claimer *Claimer
|
||||
audiences Audiences
|
||||
rootPool *x509.CertPool
|
||||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier. The name and credential id
|
||||
// should uniquely identify any X5C provisioner.
|
||||
func (p *X5C) GetID() string {
|
||||
return "x5c/" + p.Name
|
||||
}
|
||||
|
||||
// GetTokenID returns the identifier of the token.
|
||||
func (p *X5C) GetTokenID(ott string) (string, error) {
|
||||
// Validate payload
|
||||
token, err := jose.ParseSigned(ott)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error parsing token")
|
||||
}
|
||||
|
||||
// Get claims w/out verification. We need to look up the provisioner
|
||||
// key in order to verify the claims and we need the issuer from the claims
|
||||
// before we can look up the provisioner.
|
||||
var claims jose.Claims
|
||||
if err = token.UnsafeClaimsWithoutVerification(&claims); err != nil {
|
||||
return "", errors.Wrap(err, "error verifying claims")
|
||||
}
|
||||
return claims.ID, nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the provisioner.
|
||||
func (p *X5C) GetName() string {
|
||||
return p.Name
|
||||
}
|
||||
|
||||
// GetType returns the type of provisioner.
|
||||
func (p *X5C) GetType() Type {
|
||||
return TypeX5C
|
||||
}
|
||||
|
||||
// GetEncryptedKey returns the base provisioner encrypted key if it's defined.
|
||||
func (p *X5C) GetEncryptedKey() (string, string, bool) {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
// Init initializes and validates the fields of a X5C type.
|
||||
func (p *X5C) Init(config Config) error {
|
||||
switch {
|
||||
case p.Type == "":
|
||||
return errors.New("provisioner type cannot be empty")
|
||||
case p.Name == "":
|
||||
return errors.New("provisioner name cannot be empty")
|
||||
case len(p.Roots) == 0:
|
||||
return errors.New("provisioner root(s) cannot be empty")
|
||||
}
|
||||
|
||||
p.rootPool = x509.NewCertPool()
|
||||
|
||||
var (
|
||||
block *pem.Block
|
||||
rest = p.Roots
|
||||
)
|
||||
for rest != nil {
|
||||
block, rest = pem.Decode(rest)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing x509 certificate from PEM block")
|
||||
}
|
||||
p.rootPool.AddCert(cert)
|
||||
}
|
||||
|
||||
// Verify that at least one root was found.
|
||||
if len(p.rootPool.Subjects()) == 0 {
|
||||
return errors.Errorf("no x509 certificates found in roots attribute for provisioner %s", p.GetName())
|
||||
}
|
||||
|
||||
// Update claims with global ones
|
||||
var err error
|
||||
if p.claimer, err = NewClaimer(p.Claims, config.Claims); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.audiences = config.Audiences.WithFragment(p.GetID())
|
||||
return nil
|
||||
}
|
||||
|
||||
// authorizeToken performs common jwt authorization actions and returns the
|
||||
// claims for case specific downstream parsing.
|
||||
// e.g. a Sign request will auth/validate different fields than a Revoke request.
|
||||
func (p *X5C) authorizeToken(token string, audiences []string) (*x5cPayload, error) {
|
||||
jwt, err := jose.ParseSigned(token)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error parsing token")
|
||||
}
|
||||
|
||||
verifiedChains, err := jwt.Headers[0].Certificates(x509.VerifyOptions{
|
||||
Roots: p.rootPool,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error verifying x5c certificate chain")
|
||||
}
|
||||
leaf := verifiedChains[0][0]
|
||||
|
||||
if leaf.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
|
||||
return nil, errors.New("certificate used to sign x5c token cannot be used for digital signature")
|
||||
}
|
||||
|
||||
// Using the leaf certificates key to validate the claims accomplishes two
|
||||
// things:
|
||||
// 1. Asserts that the private key used to sign the token corresponds
|
||||
// to the public certificate in the `x5c` header of the token.
|
||||
// 2. Asserts that the claims are valid - have not been tampered with.
|
||||
var claims x5cPayload
|
||||
if err = jwt.Claims(leaf.PublicKey, &claims); err != nil {
|
||||
return nil, errors.Wrap(err, "error parsing claims")
|
||||
}
|
||||
|
||||
// According to "rfc7519 JSON Web Token" acceptable skew should be no
|
||||
// more than a few minutes.
|
||||
if err = claims.ValidateWithLeeway(jose.Expected{
|
||||
Issuer: p.Name,
|
||||
Time: time.Now().UTC(),
|
||||
}, time.Minute); err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid token")
|
||||
}
|
||||
|
||||
// validate audiences with the defaults
|
||||
if !matchesAudience(claims.Audience, audiences) {
|
||||
return nil, errors.New("invalid token: invalid audience claim (aud)")
|
||||
}
|
||||
|
||||
if claims.Subject == "" {
|
||||
return nil, errors.New("token subject cannot be empty")
|
||||
}
|
||||
|
||||
// Save the verified chains on the x5c payload object.
|
||||
claims.chains = verifiedChains
|
||||
return &claims, nil
|
||||
}
|
||||
|
||||
// AuthorizeRevoke returns an error if the provisioner does not have rights to
|
||||
// revoke the certificate with serial number in the `sub` property.
|
||||
func (p *X5C) AuthorizeRevoke(token string) error {
|
||||
_, err := p.authorizeToken(token, p.audiences.Revoke)
|
||||
return err
|
||||
}
|
||||
|
||||
// AuthorizeSign validates the given token.
|
||||
func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
|
||||
claims, err := p.authorizeToken(token, p.audiences.Sign)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check for SSH sign-ing request.
|
||||
if MethodFromContext(ctx) == SignSSHMethod {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID())
|
||||
}
|
||||
return p.authorizeSSHSign(claims)
|
||||
}
|
||||
|
||||
// NOTE: This is for backwards compatibility with older versions of cli
|
||||
// and certificates. Older versions added the token subject as the only SAN
|
||||
// in a CSR by default.
|
||||
if len(claims.SANs) == 0 {
|
||||
claims.SANs = []string{claims.Subject}
|
||||
}
|
||||
|
||||
dnsNames, ips, emails := x509util.SplitSANs(claims.SANs)
|
||||
|
||||
return []SignOption{
|
||||
// modifiers / withOptions
|
||||
newProvisionerExtensionOption(TypeX5C, p.Name, ""),
|
||||
profileLimitDuration{p.claimer.DefaultTLSCertDuration(), claims.chains[0][0].NotAfter},
|
||||
// validators
|
||||
commonNameValidator(claims.Subject),
|
||||
defaultPublicKeyValidator{},
|
||||
dnsNamesValidator(dnsNames),
|
||||
emailAddressesValidator(emails),
|
||||
ipAddressesValidator(ips),
|
||||
newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AuthorizeRenewal returns an error if the renewal is disabled.
|
||||
func (p *X5C) AuthorizeRenewal(cert *x509.Certificate) error {
|
||||
if p.claimer.IsDisableRenewal() {
|
||||
return errors.Errorf("renew is disabled for provisioner %s", p.GetID())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// authorizeSSHSign returns the list of SignOption for a SignSSH request.
|
||||
func (p *X5C) authorizeSSHSign(claims *x5cPayload) ([]SignOption, error) {
|
||||
if claims.Step == nil || claims.Step.SSH == nil {
|
||||
return nil, errors.New("authorization token must be an SSH provisioning token")
|
||||
}
|
||||
opts := claims.Step.SSH
|
||||
signOptions := []SignOption{
|
||||
// validates user's SSHOptions with the ones in the token
|
||||
sshCertificateOptionsValidator(*opts),
|
||||
// set the key id to the token subject
|
||||
sshCertificateKeyIDModifier(claims.Subject),
|
||||
}
|
||||
|
||||
// Add modifiers from custom claims
|
||||
if opts.CertType != "" {
|
||||
signOptions = append(signOptions, sshCertificateCertTypeModifier(opts.CertType))
|
||||
}
|
||||
if len(opts.Principals) > 0 {
|
||||
signOptions = append(signOptions, sshCertificatePrincipalsModifier(opts.Principals))
|
||||
}
|
||||
t := now()
|
||||
if !opts.ValidAfter.IsZero() {
|
||||
signOptions = append(signOptions, sshCertificateValidAfterModifier(opts.ValidAfter.RelativeTime(t).Unix()))
|
||||
}
|
||||
if !opts.ValidBefore.IsZero() {
|
||||
signOptions = append(signOptions, sshCertificateValidBeforeModifier(opts.ValidBefore.RelativeTime(t).Unix()))
|
||||
}
|
||||
|
||||
// Default to a user certificate with no principals if not set
|
||||
signOptions = append(signOptions, sshCertificateDefaultsModifier{CertType: SSHUserCert})
|
||||
|
||||
return append(signOptions,
|
||||
// Set the default extensions.
|
||||
&sshDefaultExtensionModifier{},
|
||||
// Checks the validity bounds, and set the validity if has not been set.
|
||||
sshLimitValidityModifier(p.claimer, claims.chains[0][0].NotAfter),
|
||||
// Validate public key.
|
||||
&sshDefaultPublicKeyValidator{},
|
||||
// Validate the validity period.
|
||||
&sshCertificateValidityValidator{p.claimer},
|
||||
// Require all the fields in the SSH certificate
|
||||
&sshCertificateDefaultValidator{},
|
||||
), nil
|
||||
}
|
751
authority/provisioner/x5c_test.go
Normal file
751
authority/provisioner/x5c_test.go
Normal file
|
@ -0,0 +1,751 @@
|
|||
package provisioner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
)
|
||||
|
||||
func TestX5C_Getters(t *testing.T) {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
id := "x5c/" + p.Name
|
||||
if got := p.GetID(); got != id {
|
||||
t.Errorf("X5C.GetID() = %v, want %v:%v", got, p.Name, id)
|
||||
}
|
||||
if got := p.GetName(); got != p.Name {
|
||||
t.Errorf("X5C.GetName() = %v, want %v", got, p.Name)
|
||||
}
|
||||
if got := p.GetType(); got != TypeX5C {
|
||||
t.Errorf("X5C.GetType() = %v, want %v", got, TypeX5C)
|
||||
}
|
||||
kid, key, ok := p.GetEncryptedKey()
|
||||
if kid != "" || key != "" || ok == true {
|
||||
t.Errorf("X5C.GetEncryptedKey() = (%v, %v, %v), want (%v, %v, %v)",
|
||||
kid, key, ok, "", "", false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_Init(t *testing.T) {
|
||||
type ProvisionerValidateTest struct {
|
||||
p *X5C
|
||||
err error
|
||||
extraValid func(*X5C) error
|
||||
}
|
||||
tests := map[string]func(*testing.T) ProvisionerValidateTest{
|
||||
"fail/empty": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &X5C{},
|
||||
err: errors.New("provisioner type cannot be empty"),
|
||||
}
|
||||
},
|
||||
"fail/empty-name": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &X5C{
|
||||
Type: "X5C",
|
||||
},
|
||||
err: errors.New("provisioner name cannot be empty"),
|
||||
}
|
||||
},
|
||||
"fail/empty-type": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &X5C{Name: "foo"},
|
||||
err: errors.New("provisioner type cannot be empty"),
|
||||
}
|
||||
},
|
||||
"fail/empty-key": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &X5C{Name: "foo", Type: "bar"},
|
||||
err: errors.New("provisioner root(s) cannot be empty"),
|
||||
}
|
||||
},
|
||||
"fail/no-valid-root-certs": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &X5C{Name: "foo", Type: "bar", Roots: []byte("foo"), audiences: testAudiences},
|
||||
err: errors.Errorf("no x509 certificates found in roots attribute for provisioner foo"),
|
||||
}
|
||||
},
|
||||
"fail/invalid-duration": func(t *testing.T) ProvisionerValidateTest {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
p.Claims = &Claims{DefaultTLSDur: &Duration{0}}
|
||||
return ProvisionerValidateTest{
|
||||
p: p,
|
||||
err: errors.New("claims: DefaultTLSCertDuration must be greater than 0"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) ProvisionerValidateTest {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return ProvisionerValidateTest{
|
||||
p: p,
|
||||
}
|
||||
},
|
||||
"ok/root-chain": func(t *testing.T) ProvisionerValidateTest {
|
||||
p, err := generateX5C([]byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw
|
||||
EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw
|
||||
MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl
|
||||
qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC
|
||||
AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99
|
||||
oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw
|
||||
E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1
|
||||
2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC
|
||||
lgsqsR63is+0YQ==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBhTCCASqgAwIBAgIRAMalM7pKi0GCdKjO6u88OyowCgYIKoZIzj0EAwIwFDES
|
||||
MBAGA1UEAxMJcm9vdC10ZXN0MCAXDTE5MTAwMjAyMzk0OFoYDzIxMTkwOTA4MDIz
|
||||
OTQ4WjAUMRIwEAYDVQQDEwlyb290LXRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMB
|
||||
BwNCAAS29QTCXUu7cx9sa9wZPpRSFq/zXaw8Ai3EIygayrBsKnX42U2atBUjcBZO
|
||||
BWL6A+PpLzU9ja867U5SYNHERS+Oo1swWTAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
|
||||
AQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowFAYD
|
||||
VR0RBA0wC4IJcm9vdC10ZXN0MAoGCCqGSM49BAMCA0kAMEYCIQC2vgqwla0u8LHH
|
||||
1MHob14qvS5o76HautbIBW7fcHzz5gIhAIx5A2+wkJYX4026kqaZCk/1sAwTxSGY
|
||||
M46l92gdOozT
|
||||
-----END CERTIFICATE-----`))
|
||||
assert.FatalError(t, err)
|
||||
return ProvisionerValidateTest{
|
||||
p: p,
|
||||
extraValid: func(p *X5C) error {
|
||||
numCerts := len(p.rootPool.Subjects())
|
||||
if numCerts != 2 {
|
||||
return errors.Errorf("unexpected number of certs: want 2, but got %d", numCerts)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
config := Config{
|
||||
Claims: globalProvisionerClaims,
|
||||
Audiences: testAudiences,
|
||||
}
|
||||
for name, get := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := get(t)
|
||||
err := tc.p.Init(config)
|
||||
if err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.Equals(t, tc.err.Error(), err.Error())
|
||||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
assert.Equals(t, tc.p.audiences, config.Audiences.WithFragment(tc.p.GetID()))
|
||||
if tc.extraValid != nil {
|
||||
assert.Nil(t, tc.extraValid(tc.p))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_authorizeToken(t *testing.T) {
|
||||
type test struct {
|
||||
p *X5C
|
||||
token string
|
||||
err error
|
||||
}
|
||||
tests := map[string]func(*testing.T) test{
|
||||
"fail/bad-token": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: "foo",
|
||||
err: errors.New("error parsing token"),
|
||||
}
|
||||
},
|
||||
"fail/invalid-cert-chain": func(t *testing.T) test {
|
||||
certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBpTCCAUugAwIBAgIRAOn2LHXjYyTXQ7PNjDTSKiIwCgYIKoZIzj0EAwIwHDEa
|
||||
MBgGA1UEAxMRU21hbGxzdGVwIFJvb3QgQ0EwHhcNMTkwOTE0MDk1NTM2WhcNMjkw
|
||||
OTExMDk1NTM2WjAkMSIwIAYDVQQDExlTbWFsbHN0ZXAgSW50ZXJtZWRpYXRlIENB
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2Cs0TY0dLM4b2s+z8+cc3JJp/W5H
|
||||
zQRvICX/1aJ4MuObNLcvoSguJwJEkYpGB5fhb0KvoL+ebHfEOywGNwrWkaNmMGQw
|
||||
DgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFNLJ
|
||||
4ZXoX9cI6YkGPxgs2US3ssVzMB8GA1UdIwQYMBaAFGIwpqz85wL29aF47Vj9XSVM
|
||||
P9K7MAoGCCqGSM49BAMCA0gAMEUCIQC5c1ldDcesDb31GlO5cEJvOcRrIrNtkk8m
|
||||
a5wpg+9s6QIgHIW6L60F8klQX+EO3o0SBqLeNcaskA4oSZsKjEdpSGo=
|
||||
-----END CERTIFICATE-----`))
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", p.Name, testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("error verifying x5c certificate chain: x509: certificate signed by unknown authority"),
|
||||
}
|
||||
},
|
||||
"fail/doubled-up-self-signed-cert": func(t *testing.T) test {
|
||||
certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBgjCCASigAwIBAgIQIZiE9wpmSj6SMMDfHD17qjAKBggqhkjOPQQDAjAQMQ4w
|
||||
DAYDVQQDEwVsZWFmMjAgFw0xOTEwMDIwMzEzNTlaGA8yMTE5MDkwODAzMTM1OVow
|
||||
EDEOMAwGA1UEAxMFbGVhZjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATuajJI
|
||||
3YgDaj+jorioJzGJc2+V1hUM7XzN9tIHoUeItgny9GW08TrTc23h1cCZteNZvayG
|
||||
M0wGpGeXOnE4IlH9o2IwYDAOBgNVHQ8BAf8EBAMCBSAwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBT99+JChTh3LWOHaqlSwNiwND18/zAQ
|
||||
BgNVHREECTAHggVsZWFmMjAKBggqhkjOPQQDAgNIADBFAiB7gMRy3t81HpcnoRAS
|
||||
ELZmDFaEnoLCsVfbmanFykazQQIhAI0sZjoE9t6gvzQp7XQp6CoxzCc3Jv3FwZ8G
|
||||
EXAHTA9L
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBgjCCASigAwIBAgIQIZiE9wpmSj6SMMDfHD17qjAKBggqhkjOPQQDAjAQMQ4w
|
||||
DAYDVQQDEwVsZWFmMjAgFw0xOTEwMDIwMzEzNTlaGA8yMTE5MDkwODAzMTM1OVow
|
||||
EDEOMAwGA1UEAxMFbGVhZjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATuajJI
|
||||
3YgDaj+jorioJzGJc2+V1hUM7XzN9tIHoUeItgny9GW08TrTc23h1cCZteNZvayG
|
||||
M0wGpGeXOnE4IlH9o2IwYDAOBgNVHQ8BAf8EBAMCBSAwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBT99+JChTh3LWOHaqlSwNiwND18/zAQ
|
||||
BgNVHREECTAHggVsZWFmMjAKBggqhkjOPQQDAgNIADBFAiB7gMRy3t81HpcnoRAS
|
||||
ELZmDFaEnoLCsVfbmanFykazQQIhAI0sZjoE9t6gvzQp7XQp6CoxzCc3Jv3FwZ8G
|
||||
EXAHTA9L
|
||||
-----END CERTIFICATE-----`))
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", p.Name, testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("error verifying x5c certificate chain: x509: certificate signed by unknown authority"),
|
||||
}
|
||||
},
|
||||
"fail/digital-signature-ext-required": func(t *testing.T) test {
|
||||
certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBuTCCAV+gAwIBAgIQeRJLdDMIdn/T2ORKxYABezAKBggqhkjOPQQDAjAcMRow
|
||||
GAYDVQQDExFpbnRlcm1lZGlhdGUtdGVzdDAgFw0xOTEwMDIwMjQxMTRaGA8yMTE5
|
||||
MDkwODAyNDExMlowFDESMBAGA1UEAxMJbGVhZi10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEDA1nGTOujobkcBWklyvymhWE5gQlvNLarVzhhhvPDw+MK2LX
|
||||
yqkXrYZM10GrwQZuQ7ykHnjz00U/KXpPRQ7+0qOBiDCBhTAOBgNVHQ8BAf8EBAMC
|
||||
BSAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQYv0AK
|
||||
3GUOvC+m8ZTfyhn7tKQOazAfBgNVHSMEGDAWgBSckDGJlzLaJsdy698XH32gPDMp
|
||||
czAUBgNVHREEDTALgglsZWFmLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIhAPmertx0
|
||||
lchRU3kAu647exvlhEr1xosPOu6P8kVYbtTEAiAA51w9EYIT/Zb26M3eQV817T2g
|
||||
Dnhl0ElPQsA92pkqbA==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw
|
||||
EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw
|
||||
MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl
|
||||
qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC
|
||||
AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99
|
||||
oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw
|
||||
E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1
|
||||
2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC
|
||||
lgsqsR63is+0YQ==
|
||||
-----END CERTIFICATE-----`))
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
tok, err := generateToken("", p.Name, testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("certificate used to sign x5c token cannot be used for digital signature"),
|
||||
}
|
||||
},
|
||||
"fail/signature-does-not-match-x5c-pub-key": func(t *testing.T) test {
|
||||
certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAV+gAwIBAgIQFdu723gqgGaTaqjf6ny88zAKBggqhkjOPQQDAjAcMRow
|
||||
GAYDVQQDExFpbnRlcm1lZGlhdGUtdGVzdDAgFw0xOTEwMDIwMzE4NTNaGA8yMTE5
|
||||
MDkwODAzMTg1MVowFDESMBAGA1UEAxMJbGVhZi10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEaV6807GhWEtMxA39zjuMVHAiN2/Ri5B1R1s+Y/8mlrKIvuvr
|
||||
VpgSPXYruNRFduPWX564Abz/TDmb276JbKGeQqOBiDCBhTAOBgNVHQ8BAf8EBAMC
|
||||
BaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBReMkPW
|
||||
f4MNWdg7KN4xI4ZLJd0IJDAfBgNVHSMEGDAWgBSckDGJlzLaJsdy698XH32gPDMp
|
||||
czAUBgNVHREEDTALgglsZWFmLXRlc3QwCgYIKoZIzj0EAwIDRwAwRAIgKYLKXpTN
|
||||
wtvZZaIvDzq1p8MO/SZ8yI42Ot69dNk/QtkCIBSvg5PozYcfbvwkgX5SwsjfYu0Z
|
||||
AvUgkUQ2G25NBRmX
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw
|
||||
EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw
|
||||
MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl
|
||||
qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC
|
||||
AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99
|
||||
oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw
|
||||
E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1
|
||||
2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC
|
||||
lgsqsR63is+0YQ==
|
||||
-----END CERTIFICATE-----`))
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", "foobar", testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("error parsing claims: square/go-jose: error in cryptographic primitive"),
|
||||
}
|
||||
},
|
||||
"fail/invalid-issuer": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", "foobar", testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("invalid token: square/go-jose/jwt: validation failed, invalid issuer claim (iss)"),
|
||||
}
|
||||
},
|
||||
"fail/invalid-audience": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", p.GetName(), "foobar", "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("invalid token: invalid audience claim (aud)"),
|
||||
}
|
||||
},
|
||||
"fail/empty-subject": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("token subject cannot be empty"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := tt(t)
|
||||
if claims, err := tc.p.authorizeToken(tc.token, testAudiences.Sign); err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
||||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
assert.NotNil(t, claims)
|
||||
assert.NotNil(t, claims.chains)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_AuthorizeSign(t *testing.T) {
|
||||
type test struct {
|
||||
p *X5C
|
||||
token string
|
||||
ctx context.Context
|
||||
err error
|
||||
dns []string
|
||||
emails []string
|
||||
ips []net.IP
|
||||
}
|
||||
tests := map[string]func(*testing.T) test{
|
||||
"fail/invalid-token": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: "foo",
|
||||
ctx: NewContextWithMethod(context.Background(), SignMethod),
|
||||
err: errors.New("error parsing token"),
|
||||
}
|
||||
},
|
||||
"fail/ssh/disabled": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
p.claimer.claims = provisionerClaims()
|
||||
*p.claimer.claims.EnableSSHCA = false
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
ctx: NewContextWithMethod(context.Background(), SignSSHMethod),
|
||||
token: tok,
|
||||
err: errors.Errorf("ssh ca is disabled for provisioner x5c/%s", p.GetName()),
|
||||
}
|
||||
},
|
||||
"fail/ssh/invalid-token": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
ctx: NewContextWithMethod(context.Background(), SignSSHMethod),
|
||||
token: tok,
|
||||
err: errors.New("authorization token must be an SSH provisioning token"),
|
||||
}
|
||||
},
|
||||
"ok/empty-sans": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
ctx: NewContextWithMethod(context.Background(), SignMethod),
|
||||
token: tok,
|
||||
dns: []string{"foo"},
|
||||
emails: []string{},
|
||||
ips: []net.IP{},
|
||||
}
|
||||
},
|
||||
"ok/multi-sans": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{"127.0.0.1", "foo", "max@smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
ctx: NewContextWithMethod(context.Background(), SignMethod),
|
||||
token: tok,
|
||||
dns: []string{"foo"},
|
||||
emails: []string{"max@smallstep.com"},
|
||||
ips: []net.IP{net.ParseIP("127.0.0.1")},
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := tt(t)
|
||||
if opts, err := tc.p.AuthorizeSign(tc.ctx, tc.token); err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
||||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
if assert.NotNil(t, opts) {
|
||||
tot := 0
|
||||
for _, o := range opts {
|
||||
switch v := o.(type) {
|
||||
case *provisionerExtensionOption:
|
||||
assert.Equals(t, v.Type, int(TypeX5C))
|
||||
assert.Equals(t, v.Name, tc.p.GetName())
|
||||
assert.Equals(t, v.CredentialID, "")
|
||||
assert.Len(t, 0, v.KeyValuePairs)
|
||||
case profileLimitDuration:
|
||||
assert.Equals(t, v.def, tc.p.claimer.DefaultTLSCertDuration())
|
||||
|
||||
claims, err := tc.p.authorizeToken(tc.token, tc.p.audiences.Sign)
|
||||
assert.FatalError(t, err)
|
||||
assert.Equals(t, v.notAfter, claims.chains[0][0].NotAfter)
|
||||
case commonNameValidator:
|
||||
assert.Equals(t, string(v), "foo")
|
||||
case defaultPublicKeyValidator:
|
||||
case dnsNamesValidator:
|
||||
assert.Equals(t, []string(v), tc.dns)
|
||||
case emailAddressesValidator:
|
||||
assert.Equals(t, []string(v), tc.emails)
|
||||
case ipAddressesValidator:
|
||||
assert.Equals(t, []net.IP(v), tc.ips)
|
||||
case *validityValidator:
|
||||
assert.Equals(t, v.min, tc.p.claimer.MinTLSCertDuration())
|
||||
assert.Equals(t, v.max, tc.p.claimer.MaxTLSCertDuration())
|
||||
default:
|
||||
assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v))
|
||||
}
|
||||
tot++
|
||||
}
|
||||
assert.Equals(t, tot, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_authorizeSSHSign(t *testing.T) {
|
||||
_, fn := mockNow()
|
||||
defer fn()
|
||||
type test struct {
|
||||
p *X5C
|
||||
claims *x5cPayload
|
||||
err error
|
||||
}
|
||||
tests := map[string]func(*testing.T) test{
|
||||
"fail/no-Step-claim": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
claims: new(x5cPayload),
|
||||
err: errors.New("authorization token must be an SSH provisioning token"),
|
||||
}
|
||||
},
|
||||
"fail/no-SSH-subattribute-in-claims": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
claims: &x5cPayload{Step: new(stepPayload)},
|
||||
err: errors.New("authorization token must be an SSH provisioning token"),
|
||||
}
|
||||
},
|
||||
"ok/with-claims": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
claims: &x5cPayload{
|
||||
Step: &stepPayload{SSH: &SSHOptions{
|
||||
CertType: SSHHostCert,
|
||||
Principals: []string{"max", "mariano", "alan"},
|
||||
ValidAfter: TimeDuration{d: 5 * time.Minute},
|
||||
ValidBefore: TimeDuration{d: 10 * time.Minute},
|
||||
}},
|
||||
Claims: jose.Claims{Subject: "foo"},
|
||||
chains: [][]*x509.Certificate{certs},
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/without-claims": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
claims: &x5cPayload{
|
||||
Step: &stepPayload{SSH: &SSHOptions{}},
|
||||
Claims: jose.Claims{Subject: "foo"},
|
||||
chains: [][]*x509.Certificate{certs},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := tt(t)
|
||||
if opts, err := tc.p.authorizeSSHSign(tc.claims); err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
||||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
if assert.NotNil(t, opts) {
|
||||
tot := 0
|
||||
nw := now()
|
||||
for _, o := range opts {
|
||||
switch v := o.(type) {
|
||||
case sshCertificateOptionsValidator:
|
||||
tc.claims.Step.SSH.ValidAfter.t = time.Time{}
|
||||
tc.claims.Step.SSH.ValidBefore.t = time.Time{}
|
||||
assert.Equals(t, SSHOptions(v), *tc.claims.Step.SSH)
|
||||
case sshCertificateKeyIDModifier:
|
||||
assert.Equals(t, string(v), "foo")
|
||||
case sshCertificateCertTypeModifier:
|
||||
assert.Equals(t, string(v), tc.claims.Step.SSH.CertType)
|
||||
case sshCertificatePrincipalsModifier:
|
||||
assert.Equals(t, []string(v), tc.claims.Step.SSH.Principals)
|
||||
case sshCertificateValidAfterModifier:
|
||||
assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidAfter.RelativeTime(nw).Unix())
|
||||
case sshCertificateValidBeforeModifier:
|
||||
assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix())
|
||||
case sshCertificateDefaultsModifier:
|
||||
assert.Equals(t, SSHOptions(v), SSHOptions{CertType: SSHUserCert})
|
||||
case *sshValidityModifier:
|
||||
assert.Equals(t, v.Claimer, tc.p.claimer)
|
||||
assert.Equals(t, v.validBefore, tc.claims.chains[0][0].NotAfter)
|
||||
case *sshCertificateValidityValidator:
|
||||
assert.Equals(t, v.Claimer, tc.p.claimer)
|
||||
case *sshDefaultExtensionModifier, *sshDefaultPublicKeyValidator,
|
||||
*sshCertificateDefaultValidator:
|
||||
default:
|
||||
assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v))
|
||||
}
|
||||
tot++
|
||||
}
|
||||
if len(tc.claims.Step.SSH.CertType) > 0 {
|
||||
assert.Equals(t, tot, 12)
|
||||
} else {
|
||||
assert.Equals(t, tot, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_AuthorizeRevoke(t *testing.T) {
|
||||
type test struct {
|
||||
p *X5C
|
||||
token string
|
||||
err error
|
||||
}
|
||||
tests := map[string]func(*testing.T) test{
|
||||
"fail/invalid-token": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: "foo",
|
||||
err: errors.New("error parsing token"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Revoke[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := tt(t)
|
||||
if err := tc.p.AuthorizeRevoke(tc.token); err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_AuthorizeRenewal(t *testing.T) {
|
||||
p1, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
p2, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
// disable renewal
|
||||
disable := true
|
||||
p2.Claims = &Claims{DisableRenewal: &disable}
|
||||
p2.claimer, err = NewClaimer(p2.Claims, globalProvisionerClaims)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
type args struct {
|
||||
cert *x509.Certificate
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prov *X5C
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", p1, args{nil}, false},
|
||||
{"fail", p2, args{nil}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.prov.AuthorizeRenewal(tt.args.cert); (err != nil) != tt.wantErr {
|
||||
t.Errorf("X5C.AuthorizeRenewal() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@ func withDefaultASN1DN(def *x509util.ASN1DN) x509util.WithOption {
|
|||
}
|
||||
|
||||
// Sign creates a signed certificate from a certificate signing request.
|
||||
func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) {
|
||||
func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
|
||||
var (
|
||||
errContext = apiCtx{"csr": csr, "signOptions": signOpts}
|
||||
mods = []x509util.WithOption{withDefaultASN1DN(a.config.AuthorityConfig.Template)}
|
||||
|
@ -69,66 +69,66 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti
|
|||
certValidators = append(certValidators, k)
|
||||
case provisioner.CertificateRequestValidator:
|
||||
if err := k.Valid(csr); err != nil {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "sign"), http.StatusUnauthorized, errContext}
|
||||
return nil, &apiError{errors.Wrap(err, "sign"), http.StatusUnauthorized, errContext}
|
||||
}
|
||||
case provisioner.ProfileModifier:
|
||||
mods = append(mods, k.Option(signOpts))
|
||||
default:
|
||||
return nil, nil, &apiError{errors.Errorf("sign: invalid extra option type %T", k),
|
||||
return nil, &apiError{errors.Errorf("sign: invalid extra option type %T", k),
|
||||
http.StatusInternalServerError, errContext}
|
||||
}
|
||||
}
|
||||
|
||||
if err := csr.CheckSignature(); err != nil {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "sign: invalid certificate request"),
|
||||
return nil, &apiError{errors.Wrap(err, "sign: invalid certificate request"),
|
||||
http.StatusBadRequest, errContext}
|
||||
}
|
||||
|
||||
leaf, err := x509util.NewLeafProfileWithCSR(csr, issIdentity.Crt, issIdentity.Key, mods...)
|
||||
if err != nil {
|
||||
return nil, nil, &apiError{errors.Wrapf(err, "sign"), http.StatusInternalServerError, errContext}
|
||||
return nil, &apiError{errors.Wrapf(err, "sign"), http.StatusInternalServerError, errContext}
|
||||
}
|
||||
|
||||
for _, v := range certValidators {
|
||||
if err := v.Valid(leaf.Subject()); err != nil {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "sign"), http.StatusUnauthorized, errContext}
|
||||
return nil, &apiError{errors.Wrap(err, "sign"), http.StatusUnauthorized, errContext}
|
||||
}
|
||||
}
|
||||
|
||||
crtBytes, err := leaf.CreateCertificate()
|
||||
if err != nil {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "sign: error creating new leaf certificate"),
|
||||
return nil, &apiError{errors.Wrap(err, "sign: error creating new leaf certificate"),
|
||||
http.StatusInternalServerError, errContext}
|
||||
}
|
||||
|
||||
serverCert, err := x509.ParseCertificate(crtBytes)
|
||||
if err != nil {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "sign: error parsing new leaf certificate"),
|
||||
return nil, &apiError{errors.Wrap(err, "sign: error parsing new leaf certificate"),
|
||||
http.StatusInternalServerError, errContext}
|
||||
}
|
||||
|
||||
caCert, err := x509.ParseCertificate(issIdentity.Crt.Raw)
|
||||
if err != nil {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "sign: error parsing intermediate certificate"),
|
||||
return nil, &apiError{errors.Wrap(err, "sign: error parsing intermediate certificate"),
|
||||
http.StatusInternalServerError, errContext}
|
||||
}
|
||||
|
||||
if err = a.db.StoreCertificate(serverCert); err != nil {
|
||||
if err != db.ErrNotImplemented {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "sign: error storing certificate in db"),
|
||||
return nil, &apiError{errors.Wrap(err, "sign: error storing certificate in db"),
|
||||
http.StatusInternalServerError, errContext}
|
||||
}
|
||||
}
|
||||
|
||||
return serverCert, caCert, nil
|
||||
return []*x509.Certificate{serverCert, caCert}, nil
|
||||
}
|
||||
|
||||
// Renew creates a new Certificate identical to the old certificate, except
|
||||
// with a validity window that begins 'now'.
|
||||
func (a *Authority) Renew(oldCert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) {
|
||||
func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error) {
|
||||
// Check step provisioner extensions
|
||||
if err := a.authorizeRenewal(oldCert); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Issuer
|
||||
|
@ -181,26 +181,26 @@ func (a *Authority) Renew(oldCert *x509.Certificate) (*x509.Certificate, *x509.C
|
|||
leaf, err := x509util.NewLeafProfileWithTemplate(newCert,
|
||||
issIdentity.Crt, issIdentity.Key)
|
||||
if err != nil {
|
||||
return nil, nil, &apiError{err, http.StatusInternalServerError, apiCtx{}}
|
||||
return nil, &apiError{err, http.StatusInternalServerError, apiCtx{}}
|
||||
}
|
||||
crtBytes, err := leaf.CreateCertificate()
|
||||
if err != nil {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "error renewing certificate from existing server certificate"),
|
||||
return nil, &apiError{errors.Wrap(err, "error renewing certificate from existing server certificate"),
|
||||
http.StatusInternalServerError, apiCtx{}}
|
||||
}
|
||||
|
||||
serverCert, err := x509.ParseCertificate(crtBytes)
|
||||
if err != nil {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "error parsing new server certificate"),
|
||||
return nil, &apiError{errors.Wrap(err, "error parsing new server certificate"),
|
||||
http.StatusInternalServerError, apiCtx{}}
|
||||
}
|
||||
caCert, err := x509.ParseCertificate(issIdentity.Crt.Raw)
|
||||
if err != nil {
|
||||
return nil, nil, &apiError{errors.Wrap(err, "error parsing intermediate certificate"),
|
||||
return nil, &apiError{errors.Wrap(err, "error parsing intermediate certificate"),
|
||||
http.StatusInternalServerError, apiCtx{}}
|
||||
}
|
||||
|
||||
return serverCert, caCert, nil
|
||||
return []*x509.Certificate{serverCert, caCert}, nil
|
||||
}
|
||||
|
||||
// RevokeOptions are the options for the Revoke API.
|
||||
|
|
|
@ -208,14 +208,15 @@ func TestSign(t *testing.T) {
|
|||
},
|
||||
"fail rsa key too short": func(t *testing.T) *signTest {
|
||||
shortRSAKeyPEM := `-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBdDCB2wIBADAOMQwwCgYDVQQDEwNmb28wgaIwDQYJKoZIhvcNAQEBBQADgZAA
|
||||
MIGMAoGEAK8dks7oV6kcIFEaWna7CDGYPAE8IL7rNi+ruQ1dIYz+JtxT7OPjbCn/
|
||||
t5iqni96+35iS/8CvMtEuquOMTMSWOWwlurrbTbLqCazuz/g233o8udxSxhny3cY
|
||||
wHogp4cXCX6cFll6DeUnoCEuTTSIu8IBHbK48VfNw4V4gGz6cp/H93HrAgMBAAGg
|
||||
ITAfBgkqhkiG9w0BCQ4xEjAQMA4GA1UdEQQHMAWCA2ZvbzANBgkqhkiG9w0BAQsF
|
||||
AAOBhABCZsYM+Kgje68Z9Fjl2+cBwtQHvZDarh+cz6W1SchinZ1T0aNQvSj/otOe
|
||||
ttnEF4Rq8zqzr4fbv+AF451Mx36AkfgZr9XWGzxidrH+fBCNWXWNR+ymhrL6UFTG
|
||||
2FbarLt9jN2aJLAYQPwtSeGTAZ74tLOPRPnTP6aMfFNg4XCR0uveHA==
|
||||
MIIBhDCB7gIBADAZMRcwFQYDVQQDEw5zbWFsbHN0ZXAgdGVzdDCBnzANBgkqhkiG
|
||||
9w0BAQEFAAOBjQAwgYkCgYEA5JlgH99HvHHsCD6XTqqYj3bXU2oIlnYGoLVs7IJ4
|
||||
k205rv5/YWky2gjdpIv0Tnaf3o57IJ891lB7GiyO5iHIEUv5N9dVzrdUboyzk2uZ
|
||||
7JMMNB43CSLB2oNuwJjLeAM/yBzlhRnvpKjrNSfSV+cH54FXdnbFbcTFMStnjqKG
|
||||
MeECAwEAAaAsMCoGCSqGSIb3DQEJDjEdMBswGQYDVR0RBBIwEIIOc21hbGxzdGVw
|
||||
IHRlc3QwDQYJKoZIhvcNAQELBQADgYEAKwsbr8Zfcq05DgOoJ//cXMFK1SP8ktRU
|
||||
N2++E8Ww0Tet9oyNRArqxxS/UyVio63D3wynzRAB25PFGpYG1cN4b81Gv/foFUT6
|
||||
W5kR63lNVHBHgQmv5mA8YFsfrJHstaz5k727v2LMHEYIf5/3i16d5zhuxUoaPTYr
|
||||
ZYtQ9Ot36qc=
|
||||
-----END CERTIFICATE REQUEST-----`
|
||||
block, _ := pem.Decode([]byte(shortRSAKeyPEM))
|
||||
assert.FatalError(t, err)
|
||||
|
@ -276,7 +277,7 @@ ttnEF4Rq8zqzr4fbv+AF451Mx36AkfgZr9XWGzxidrH+fBCNWXWNR+ymhrL6UFTG
|
|||
t.Run(name, func(t *testing.T) {
|
||||
tc := genTestCase(t)
|
||||
|
||||
leaf, intermediate, err := tc.auth.Sign(tc.csr, tc.signOpts, tc.extraOpts...)
|
||||
certChain, err := tc.auth.Sign(tc.csr, tc.signOpts, tc.extraOpts...)
|
||||
if err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
switch v := err.(type) {
|
||||
|
@ -289,6 +290,8 @@ ttnEF4Rq8zqzr4fbv+AF451Mx36AkfgZr9XWGzxidrH+fBCNWXWNR+ymhrL6UFTG
|
|||
}
|
||||
}
|
||||
} else {
|
||||
leaf := certChain[0]
|
||||
intermediate := certChain[1]
|
||||
if assert.Nil(t, tc.err) {
|
||||
assert.Equals(t, leaf.NotBefore, signOpts.NotBefore.Time().Truncate(time.Second))
|
||||
assert.Equals(t, leaf.NotAfter, signOpts.NotAfter.Time().Truncate(time.Second))
|
||||
|
@ -453,11 +456,11 @@ func TestRenew(t *testing.T) {
|
|||
tc, err := genTestCase()
|
||||
assert.FatalError(t, err)
|
||||
|
||||
var leaf, intermediate *x509.Certificate
|
||||
var certChain []*x509.Certificate
|
||||
if tc.auth != nil {
|
||||
leaf, intermediate, err = tc.auth.Renew(tc.crt)
|
||||
certChain, err = tc.auth.Renew(tc.crt)
|
||||
} else {
|
||||
leaf, intermediate, err = a.Renew(tc.crt)
|
||||
certChain, err = a.Renew(tc.crt)
|
||||
}
|
||||
if err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
|
@ -471,6 +474,8 @@ func TestRenew(t *testing.T) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
leaf := certChain[0]
|
||||
intermediate := certChain[1]
|
||||
if assert.Nil(t, tc.err) {
|
||||
assert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.crt.NotAfter.Sub(crt.NotBefore))
|
||||
|
||||
|
|
|
@ -303,7 +303,7 @@ func TestBootstrapClient(t *testing.T) {
|
|||
t.Errorf("BootstrapClient() error reading response: %v", err)
|
||||
return
|
||||
}
|
||||
if renewal.CaPEM.Certificate == nil || renewal.ServerPEM.Certificate == nil {
|
||||
if renewal.CaPEM.Certificate == nil || renewal.ServerPEM.Certificate == nil || len(renewal.CertChainPEM) == 0 {
|
||||
t.Errorf("BootstrapClient() invalid renewal response: %v", renewal)
|
||||
}
|
||||
}
|
||||
|
@ -375,7 +375,7 @@ func TestBootstrapClientServerRotation(t *testing.T) {
|
|||
if err := readJSON(resp.Body, &renew); err != nil {
|
||||
return errors.Wrap(err, "client.Post() error reading response")
|
||||
}
|
||||
if renew.ServerPEM.Certificate == nil || renew.CaPEM.Certificate == nil {
|
||||
if renew.ServerPEM.Certificate == nil || renew.CaPEM.Certificate == nil || len(renew.CertChainPEM) == 0 {
|
||||
return errors.New("client.Post() unexpected response found")
|
||||
}
|
||||
// test with bootstrap server
|
||||
|
@ -492,7 +492,7 @@ func TestBootstrapClientServerFederation(t *testing.T) {
|
|||
if err := readJSON(resp.Body, &renew); err != nil {
|
||||
return errors.Wrap(err, "client.Post() error reading response")
|
||||
}
|
||||
if renew.ServerPEM.Certificate == nil || renew.CaPEM.Certificate == nil {
|
||||
if renew.ServerPEM.Certificate == nil || renew.CaPEM.Certificate == nil || len(renew.CertChainPEM) == 0 {
|
||||
return errors.New("client.Post() unexpected response found")
|
||||
}
|
||||
// test with bootstrap server
|
||||
|
|
5
ca/ca.go
5
ca/ca.go
|
@ -124,7 +124,10 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) {
|
|||
}
|
||||
|
||||
prefix := "acme"
|
||||
acmeAuth := acme.NewAuthority(auth.GetDatabase().(nosql.DB), dns, prefix, auth)
|
||||
acmeAuth, err := acme.NewAuthority(auth.GetDatabase().(nosql.DB), dns, prefix, auth)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating ACME authority")
|
||||
}
|
||||
acmeRouterHandler := acmeAPI.New(acmeAuth)
|
||||
mux.Route("/"+prefix, func(r chi.Router) {
|
||||
acmeRouterHandler.Route(r)
|
||||
|
|
|
@ -265,6 +265,10 @@ func TestClient_Sign(t *testing.T) {
|
|||
ok := &api.SignResponse{
|
||||
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)},
|
||||
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)},
|
||||
CertChainPEM: []api.Certificate{
|
||||
{Certificate: parseCertificate(certPEM)},
|
||||
{Certificate: parseCertificate(rootPEM)},
|
||||
},
|
||||
}
|
||||
request := &api.SignRequest{
|
||||
CsrPEM: api.CertificateRequest{CertificateRequest: parseCertificateRequest(csrPEM)},
|
||||
|
@ -418,6 +422,10 @@ func TestClient_Renew(t *testing.T) {
|
|||
ok := &api.SignResponse{
|
||||
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)},
|
||||
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)},
|
||||
CertChainPEM: []api.Certificate{
|
||||
{Certificate: parseCertificate(certPEM)},
|
||||
{Certificate: parseCertificate(rootPEM)},
|
||||
},
|
||||
}
|
||||
unauthorized := api.Unauthorized(fmt.Errorf("Unauthorized"))
|
||||
badRequest := api.BadRequest(fmt.Errorf("Bad Request"))
|
||||
|
|
|
@ -178,7 +178,7 @@ func (r *TLSRenewer) renewCertificate() {
|
|||
}
|
||||
|
||||
func (r *TLSRenewer) nextRenewDuration(notAfter time.Time) time.Duration {
|
||||
d := notAfter.Sub(time.Now()) - r.renewBefore
|
||||
d := time.Until(notAfter) - r.renewBefore
|
||||
n := rand.Int63n(int64(r.renewJitter))
|
||||
d -= time.Duration(n)
|
||||
if d < 0 {
|
||||
|
|
10
ca/signal.go
10
ca/signal.go
|
@ -28,9 +28,7 @@ func StopHandler(servers ...Stopper) {
|
|||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer signal.Stop(signals)
|
||||
|
||||
for {
|
||||
select {
|
||||
case sig := <-signals:
|
||||
for sig := range signals {
|
||||
switch sig {
|
||||
case syscall.SIGINT, syscall.SIGTERM:
|
||||
log.Println("shutting down ...")
|
||||
|
@ -43,7 +41,6 @@ func StopHandler(servers ...Stopper) {
|
|||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StopReloaderHandler watches SIGINT, SIGTERM and SIGHUP on a list of servers
|
||||
|
@ -54,9 +51,7 @@ func StopReloaderHandler(servers ...StopReloader) {
|
|||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
defer signal.Stop(signals)
|
||||
|
||||
for {
|
||||
select {
|
||||
case sig := <-signals:
|
||||
for sig := range signals {
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
log.Println("reloading ...")
|
||||
|
@ -77,5 +72,4 @@ func StopReloaderHandler(servers ...StopReloader) {
|
|||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -553,7 +553,7 @@ func equalPools(a, b *x509.CertPool) bool {
|
|||
for i := range subjects {
|
||||
sB[i] = string(subjects[i])
|
||||
}
|
||||
sort.Sort(sort.StringSlice(sA))
|
||||
sort.Sort(sort.StringSlice(sB))
|
||||
sort.Strings(sA)
|
||||
sort.Strings(sB)
|
||||
return reflect.DeepEqual(sA, sB)
|
||||
}
|
||||
|
|
|
@ -417,6 +417,10 @@ func TestCertificate(t *testing.T) {
|
|||
ok := &api.SignResponse{
|
||||
ServerPEM: api.Certificate{Certificate: cert},
|
||||
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)},
|
||||
CertChainPEM: []api.Certificate{
|
||||
{Certificate: cert},
|
||||
{Certificate: parseCertificate(rootPEM)},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -446,6 +450,10 @@ func TestIntermediateCertificate(t *testing.T) {
|
|||
ok := &api.SignResponse{
|
||||
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)},
|
||||
CaPEM: api.Certificate{Certificate: intermediate},
|
||||
CertChainPEM: []api.Certificate{
|
||||
{Certificate: parseCertificate(certPEM)},
|
||||
{Certificate: intermediate},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -475,6 +483,10 @@ func TestRootCertificateCertificate(t *testing.T) {
|
|||
ok := &api.SignResponse{
|
||||
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)},
|
||||
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)},
|
||||
CertChainPEM: []api.Certificate{
|
||||
{Certificate: parseCertificate(certPEM)},
|
||||
{Certificate: parseCertificate(rootPEM)},
|
||||
},
|
||||
TLS: &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{
|
||||
{root, root},
|
||||
}},
|
||||
|
@ -482,6 +494,10 @@ func TestRootCertificateCertificate(t *testing.T) {
|
|||
noTLS := &api.SignResponse{
|
||||
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)},
|
||||
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)},
|
||||
CertChainPEM: []api.Certificate{
|
||||
{Certificate: parseCertificate(certPEM)},
|
||||
{Certificate: parseCertificate(rootPEM)},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
@ -26,7 +26,7 @@ e.g. `v1.0.2`
|
|||
2. **Update the version of step/cli**
|
||||
|
||||
<pre><code>
|
||||
<b>$ dep ensure -update github.com/smallstep/cli</b>
|
||||
<b>$ go get -u github.com/smallstep/cli</b>
|
||||
</code></pre>
|
||||
|
||||
3. **Commit all changes.**
|
||||
|
|
|
@ -176,7 +176,7 @@ created when you initilialized your PKI. In order to properly validate this
|
|||
certificate clients need access to the public root of trust, aka the public root
|
||||
certificate. If you are using the `step cli` on the same host where you
|
||||
initialized your PKI (the `root_ca.crt` is stored on disk locally), then you can
|
||||
continue to [setting up your environment](setting-up-environment-variables),
|
||||
continue to [setting up your environment](#setup-env),
|
||||
otherwise we will show you how to easily download your root certificate in the
|
||||
following step.
|
||||
|
||||
|
@ -215,9 +215,10 @@ In the examples below we will use `https://ca.smallstep.com:8080`.
|
|||
3. Test.
|
||||
|
||||
```
|
||||
* step ca health
|
||||
$ step ca health
|
||||
```
|
||||
|
||||
<a name="setup-env"></a>
|
||||
#### Setting up Environment Defaults
|
||||
|
||||
This is optional, but we recommend you populate a `defaults.json` file with a
|
||||
|
|
|
@ -71,8 +71,8 @@ There are two ways to address this problem:
|
|||
|
||||
1. Explicitly configure your ACME client to trust `step-ca`'s root certificate, or
|
||||
2. Add `step-ca`'s root certificate to your system’s default trust store (e.g.,
|
||||
using `[step certificate
|
||||
install](https://smallstep.com/docs/cli/certificate/install/)`)
|
||||
using [`step certificate
|
||||
install`](https://smallstep.com/docs/cli/certificate/install/))
|
||||
|
||||
If you’re using your CA for TLS in production, explicitly configuring your ACME
|
||||
client to only trust your root certificate is a better option. We’ll
|
||||
|
|
|
@ -132,7 +132,7 @@ is G-Suite.
|
|||
provider used to get the id token. Some identity providers might use an empty
|
||||
string as a secret.
|
||||
|
||||
* `configurationEndpoing` (mandatory): is the HTTP address used by the CA to get
|
||||
* `configurationEndpoint` (mandatory): is the HTTP address used by the CA to get
|
||||
the OpenID Connect configuration and public keys used to validate the tokens.
|
||||
|
||||
* `admins` (optional): is the list of emails that will be able to get
|
||||
|
|
44
go.mod
Normal file
44
go.mod
Normal file
|
@ -0,0 +1,44 @@
|
|||
module github.com/smallstep/certificates
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 // indirect
|
||||
github.com/Masterminds/goutils v1.1.0 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/chzyer/logex v1.1.10 // indirect
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
||||
github.com/dgraph-io/badger v1.5.3 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect
|
||||
github.com/go-chi/chi v3.3.4-0.20181024101233-0ebf7795c516+incompatible
|
||||
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
||||
github.com/golangci/golangci-lint v1.18.0 // indirect
|
||||
github.com/google/uuid v1.1.1 // indirect
|
||||
github.com/huandu/xstrings v1.2.0 // indirect
|
||||
github.com/imdario/mergo v0.3.8 // indirect
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a // indirect
|
||||
github.com/manifoldco/promptui v0.3.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.4 // indirect
|
||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||
github.com/newrelic/go-agent v1.11.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/rs/xid v1.2.1
|
||||
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect
|
||||
github.com/sirupsen/logrus v1.1.1
|
||||
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5
|
||||
github.com/smallstep/cli v0.13.4-0.20191014220846-775cfe98ef76
|
||||
github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61
|
||||
github.com/urfave/cli v1.20.1-0.20181029213200-b67dcf995b6a
|
||||
go.etcd.io/bbolt v1.3.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||
google.golang.org/appengine v1.5.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.3.1
|
||||
)
|
285
go.sum
Normal file
285
go.sum
Normal file
|
@ -0,0 +1,285 @@
|
|||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.0 h1:k9QF73nrHT3nPLz3lu6G5s+3Hi8Je36ODr1F5gjAXXM=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger v1.5.3 h1:5oWIuRvwn93cie+OSt1zSnkaIQ1JFQM8bGlIv6O6Sts=
|
||||
github.com/dgraph-io/badger v1.5.3/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/fatih/color v1.6.0 h1:66qjqZk8kalYAvDRtM1AdAJQI0tj4Wrue3Eq3B3pmFU=
|
||||
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-chi/chi v3.3.4-0.20181024101233-0ebf7795c516+incompatible h1:QkUV3XfIQZlGH/Y84jpL20do5cooBfUMzPRNRZvVkZ0=
|
||||
github.com/go-chi/chi v3.3.4-0.20181024101233-0ebf7795c516+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540 h1:djv/qAomOVj8voCHt0M0OYwR/4vfDq1zNKSPKjJCexs=
|
||||
github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
|
||||
github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0=
|
||||
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
|
||||
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
||||
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
|
||||
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
|
||||
github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
|
||||
github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ=
|
||||
github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
|
||||
github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg=
|
||||
github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k=
|
||||
github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
|
||||
github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
|
||||
github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk=
|
||||
github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg=
|
||||
github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
|
||||
github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks=
|
||||
github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
|
||||
github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4=
|
||||
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
|
||||
github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA=
|
||||
github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/mock v1.0.0 h1:HzcpUG60pfl43n9d2qbdi/3l1uKpAmxlfWEPWtV/QxM=
|
||||
github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0=
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM=
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
|
||||
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w=
|
||||
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
|
||||
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw=
|
||||
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
|
||||
github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c h1:/7detzz5stiXWPzkTlPTzkBEIIE4WGpppBJYjKqBiPI=
|
||||
github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM=
|
||||
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8=
|
||||
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
|
||||
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8=
|
||||
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
|
||||
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98 h1:0OkFarm1Zy2CjCiDKfK9XHgmc2wbDlRMD2hD8anAJHU=
|
||||
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
||||
github.com/golangci/golangci-lint v1.18.0 h1:XmQgfcLofSG/6AsQuQqmLizB+3GggD+o6ObBG9L+VMM=
|
||||
github.com/golangci/golangci-lint v1.18.0/go.mod h1:kaqo8l0OZKYPtjNmG4z4HrWLgcYNIJ9B9q3LWri9uLg=
|
||||
github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547 h1:fUdgm/BdKvwOHxg5AhNbkNRp2mSy8sxTXyBVs/laQHo=
|
||||
github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU=
|
||||
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI=
|
||||
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
|
||||
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217 h1:En/tZdwhAn0JNwLuXzP3k2RVtMqMmOEK7Yu/g3tmtJE=
|
||||
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
|
||||
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
|
||||
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
|
||||
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk=
|
||||
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
|
||||
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us=
|
||||
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
|
||||
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg=
|
||||
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
|
||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys=
|
||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
|
||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
||||
github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM=
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/magiconair/properties v1.7.6 h1:U+1DqNen04MdEPgFiIwdOUiqZ8qPa37xgogX/sd3+54=
|
||||
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/manifoldco/promptui v0.3.1 h1:BxqNa7q1hVHXIXy3iupJMkXYS3aHhbubJWv2Jmg6x64=
|
||||
github.com/manifoldco/promptui v0.3.1/go.mod h1:zoCNXiJnyM03LlBgTsWv8mq28s7aTC71UgKasqRJHww=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238 h1:+MZW2uvHgN8kYvksEN3f7eFL2wpzk0GxmlFsMybWc7E=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663 h1:Ri1EhipkbhWsffPJ3IPlrb4SkTOPa2PfRXp3jchBczw=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/newrelic/go-agent v1.11.0 h1:jnd8+H6dB+93UTJHFT1wJoij5spKNN/xZ0nkw0kvt7o=
|
||||
github.com/newrelic/go-agent v1.11.0/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pelletier/go-toml v1.1.0 h1:cmiOvKzEunMsAxyhXSzpL5Q1CRKpVv0KQsnAIcSEVYM=
|
||||
github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189 h1:CmSpbxmewNQbzqztaY0bke1qzHhyNyC29wYgh17Gxfo=
|
||||
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189/go.mod h1:UUwuHEJ9zkkPDxspIHOa59PUeSkGFljESGzbxntLmIg=
|
||||
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg=
|
||||
github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
|
||||
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5 h1:lX6ybsQW9Agn3qK/W1Z39Z4a6RyEMGem/gXUYW0axYk=
|
||||
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE=
|
||||
github.com/smallstep/cli v0.13.4-0.20191014220846-775cfe98ef76 h1:iNh6x3czCb/01npI8/o4UdvfmTXJkjwVsBOfcXWvmAs=
|
||||
github.com/smallstep/cli v0.13.4-0.20191014220846-775cfe98ef76/go.mod h1:zGPm8vWCqzvDqkdC1laFJNdIOjNSB8V4qDp68Ny538o=
|
||||
github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61 h1:XM3mkHNBc6bEQhrZNEma+iz63xrmRFfCocmAEObeg/s=
|
||||
github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61/go.mod h1:MFhYHIE/0V7OOHjYzjnWHqySJ40PVbwhjy24UBkJI2g=
|
||||
github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs=
|
||||
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
|
||||
github.com/spf13/afero v1.1.0 h1:bopulORc2JeYaxfHLvJa5NzxviA9PoWhpiiJkru7Ji4=
|
||||
github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
|
||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/cobra v0.0.2 h1:NfkwRbgViGoyjBKsLI0QMDcuMnhM+SBg3T0cGfpvKDE=
|
||||
github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec h1:2ZXvIUGghLpdTVHR1UfvfrzoVlZaE/yOWC5LueIHZig=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.0.2 h1:Ncr3ZIuJn322w2k1qmzXDnkLAdQMlJqBa9kfAH+irso=
|
||||
github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec h1:AmoEvWAO3nDx1MEcMzPh+GzOOIA5Znpv6++c7bePPY0=
|
||||
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||
github.com/ultraware/funlen v0.0.1 h1:UeC9tpM4wNWzUJfan8z9sFE4QCzjjzlCZmuJN+aOkH0=
|
||||
github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
|
||||
github.com/urfave/cli v1.20.1-0.20181029213200-b67dcf995b6a h1:qbTm+Zobir+JOKt4xjwK7rwNJXWVfHtV0zGf4TVJ1tQ=
|
||||
github.com/urfave/cli v1.20.1-0.20181029213200-b67dcf995b6a/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
|
||||
github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816 h1:mVFkLpejdFLXVUv9E42f3XJVfMdqd0IVLVIVLjZWn5o=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc h1:SdCq5U4J+PpbSDIl9bM0V1e1Ug1jsnBkAFvTs1htn7U=
|
||||
golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190909030654-5b82db07426d h1:PhtdWYteEBebOX7KXm4qkIAVSUTHQ883/2hRB92r9lk=
|
||||
golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo=
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||
mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34 h1:duVSyluuJA+u0BnkcLR01smoLrGgDTfWt5c8ODYG8fU=
|
||||
mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
|
@ -32,7 +32,7 @@ func (l *LoggerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
t := time.Now()
|
||||
rw := NewResponseLogger(w)
|
||||
l.next.ServeHTTP(rw, r)
|
||||
d := time.Now().Sub(t)
|
||||
d := time.Since(t)
|
||||
l.writeEntry(rw, r, t, d)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue