Adding Keyvault Shim (#1346)
* adding az sdk dependencies and tidying mod file * adding keyvault shim * example usage application for kv shim * adding tests, cleaning up * fixing linter errors * updating go mod
This commit is contained in:
Родитель
c3e709d2af
Коммит
5e6f76aaf7
76
go.mod
76
go.mod
|
@ -3,14 +3,14 @@ module github.com/Azure/azure-container-networking
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
code.cloudfoundry.org/clock v1.0.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.7.1
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Microsoft/go-winio v0.4.17
|
||||
github.com/Microsoft/hcsshim v0.8.23
|
||||
github.com/avast/retry-go/v3 v3.1.1
|
||||
github.com/billgraziano/dpapi v0.4.0
|
||||
github.com/containernetworking/cni v0.8.1
|
||||
github.com/docker/docker v20.10.8+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/libnetwork v0.8.0-dev.2.0.20210525090646-64b7a4574d14
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/golang/protobuf v1.5.2
|
||||
|
@ -18,7 +18,6 @@ require (
|
|||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/hashicorp/go-version v1.5.0
|
||||
github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee // indirect
|
||||
github.com/microsoft/ApplicationInsights-Go v0.4.4
|
||||
github.com/nxadm/tail v1.4.8
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
|
@ -30,9 +29,8 @@ require (
|
|||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.12.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
||||
go.uber.org/zap v1.21.0
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
google.golang.org/grpc v1.46.2
|
||||
google.golang.org/protobuf v1.28.0
|
||||
k8s.io/api v0.24.1
|
||||
|
@ -40,35 +38,60 @@ require (
|
|||
k8s.io/apimachinery v0.24.1
|
||||
k8s.io/client-go v0.24.1
|
||||
k8s.io/klog v1.0.0
|
||||
k8s.io/klog/v2 v2.60.1
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
|
||||
sigs.k8s.io/controller-runtime v0.12.1
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/avast/retry-go/v3 v3.1.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
code.cloudfoundry.org/clock v1.0.0 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/cgroups v1.0.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/docker v20.10.8+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-logr/logr v1.2.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/gofrs/uuid v3.3.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hpcloud/tail v1.0.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/labstack/echo/v4 v4.7.2
|
||||
github.com/labstack/gommon v0.3.1 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
|
@ -77,9 +100,16 @@ require (
|
|||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.3.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88
|
||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
|
||||
|
@ -93,39 +123,9 @@ require (
|
|||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0 // indirect
|
||||
k8s.io/component-base v0.24.1 // indirect
|
||||
k8s.io/klog/v2 v2.60.1
|
||||
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
)
|
||||
|
||||
require go.uber.org/zap v1.21.0
|
||||
|
||||
require (
|
||||
github.com/gofrs/uuid v3.3.0+incompatible // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/labstack/echo/v4 v4.7.2
|
||||
github.com/labstack/gommon v0.3.1 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
|
|
19
go.sum
19
go.sum
|
@ -44,7 +44,17 @@ code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c/go.mod h1:QD9Lzhd
|
|||
code.cloudfoundry.org/clock v1.0.0 h1:kFXWQM4bxYvdBw2X8BbBeXwQNgfoWv1vqAk2ZZyBN2o=
|
||||
code.cloudfoundry.org/clock v1.0.0/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible h1:KnPIugL51v3N3WwvaSmZbxukD1WuWXOiE9fRdu32f2I=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0 h1:sVPhtT2qjO86rTUaWMr4WoES4TkjGnzcioXcnHV9s5k=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0 h1:Yoicul8bnVdQrhDMTHxdEckRGX01XvwXDHUT9zYZ3k0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.7.1 h1:X7FHRMKr0u5YiPnD6L/nqG64XBOcK0IYavhAHBQEmms=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.7.1/go.mod h1:WcC2Tk6JyRlqjn2byvinNnZzgdXmZ1tOiIOWNh1u0uA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 h1:9cn6ICCGiWFNA/slKnrkf+ENyvaCRKHtuoGtnLIAgao=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
|
@ -60,6 +70,7 @@ github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935
|
|||
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 h1:WVsrXCnHlDDX8ls+tootqRE87/hL9S/g4ewig9RsD/c=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
|
@ -259,6 +270,7 @@ github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11
|
|||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c=
|
||||
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
|
@ -364,6 +376,7 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
|
|||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -550,6 +563,7 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI=
|
||||
github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
|
||||
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
|
||||
|
@ -663,6 +677,7 @@ github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
|
|||
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
@ -898,8 +913,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 h1:Tgea0cVUD0ivh5ADBX4WwuI12DUd2to3nCYe2eayMIw=
|
||||
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-container-networking/keyvault"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
const serverAddr = "127.0.0.1:9005"
|
||||
|
||||
var logger *zap.Logger
|
||||
|
||||
func mustArgs() (kvURL string, kvCert string) {
|
||||
flag.StringVar(&kvURL, "keyvault-url", "", "keyvault url")
|
||||
flag.StringVar(&kvCert, "keyvault-cert-name", "", "keyvault certificate name")
|
||||
flag.Parse()
|
||||
if kvURL == "" || kvCert == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
core := zapcore.NewCore(zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), os.Stdout, zap.DebugLevel)
|
||||
logger = zap.New(core)
|
||||
return
|
||||
}
|
||||
|
||||
// you must be logged in via the az cli and have proper permissions to a keyvault to run this example
|
||||
func main() {
|
||||
kvURL, kvCert := mustArgs()
|
||||
cred, err := azidentity.NewDefaultAzureCredential(nil)
|
||||
if err != nil {
|
||||
logger.Fatal("could not create credentials", zap.Error(err))
|
||||
}
|
||||
|
||||
kvs, err := keyvault.NewShim(kvURL, cred)
|
||||
if err != nil {
|
||||
logger.Fatal("could not create keyvault client", zap.Error(err))
|
||||
}
|
||||
|
||||
tlsCert, err := kvs.GetLatestTLSCertificate(context.TODO(), kvCert)
|
||||
if err != nil {
|
||||
logger.Fatal("could not get tls cert from keyvault", zap.Error(err))
|
||||
}
|
||||
|
||||
clientTLSConfig, err := createClientTLSConfig(tlsCert)
|
||||
if err != nil {
|
||||
logger.Fatal("could not create client tls config", zap.Error(err))
|
||||
}
|
||||
|
||||
server := http.Server{
|
||||
Addr: serverAddr,
|
||||
Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
_, _ = writer.Write([]byte("hello"))
|
||||
}),
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
ClientCAs: clientTLSConfig.RootCAs,
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
},
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := server.ListenAndServeTLS("", ""); err != nil {
|
||||
logger.Fatal("could not serve tls", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for a short time to allow server to start
|
||||
time.Sleep(time.Second)
|
||||
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: clientTLSConfig,
|
||||
},
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("https://%s", serverAddr)
|
||||
resp, err := client.Get(addr)
|
||||
if err != nil {
|
||||
logger.Fatal("could not get response", zap.String("host", addr), zap.Error(err))
|
||||
}
|
||||
|
||||
printTLSConnState(resp.TLS)
|
||||
|
||||
bs, _ := io.ReadAll(resp.Body)
|
||||
logger.Info("response from tls server", zap.String("body bytes", string(bs)))
|
||||
}
|
||||
|
||||
func createClientTLSConfig(tlsCert tls.Certificate) (*tls.Config, error) {
|
||||
certs := x509.NewCertPool()
|
||||
|
||||
if len(tlsCert.Certificate) == 1 { // self signed
|
||||
cer, err := x509.ParseCertificate(tlsCert.Certificate[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs.AddCert(cer)
|
||||
return &tls.Config{RootCAs: certs, ServerName: tlsCert.Leaf.Subject.CommonName}, nil
|
||||
}
|
||||
|
||||
for i, bytes := range tlsCert.Certificate {
|
||||
if i == 0 {
|
||||
continue // skip leaf
|
||||
}
|
||||
cer, err := x509.ParseCertificate(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs.AddCert(cer)
|
||||
}
|
||||
|
||||
return &tls.Config{Certificates: []tls.Certificate{tlsCert}, RootCAs: certs, ServerName: tlsCert.Leaf.Subject.CommonName}, nil
|
||||
}
|
||||
|
||||
func printTLSConnState(connState *tls.ConnectionState) {
|
||||
logger.Info("response tls connection state", zap.Object("conn state", loggableConnState(*connState)))
|
||||
|
||||
for i, cert := range connState.PeerCertificates {
|
||||
logger.Info(fmt.Sprintf("peer certificate %d:", i), zap.Stringer("subject", cert.Subject), zap.Stringer("issuer", cert.Issuer))
|
||||
}
|
||||
|
||||
for i, chain := range connState.VerifiedChains {
|
||||
for j, cert := range chain {
|
||||
logger.Info(fmt.Sprintf("chain %d, cert %d:", i, j), zap.Stringer("subject", cert.Subject), zap.Stringer("issuer", cert.Issuer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type loggableConnState tls.ConnectionState
|
||||
|
||||
func (l loggableConnState) MarshalLogObject(encoder zapcore.ObjectEncoder) error {
|
||||
encoder.AddString("server name", l.ServerName)
|
||||
encoder.AddBool("handshake complete", l.HandshakeComplete)
|
||||
encoder.AddInt("peer certificates", len(l.PeerCertificates))
|
||||
encoder.AddInt("verified certificates", len(l.VerifiedChains))
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package keyvault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/pkcs12"
|
||||
)
|
||||
|
||||
const (
|
||||
pemContentType = "application/x-pem-file"
|
||||
pkcs12ContentType = "application/x-pkcs12"
|
||||
)
|
||||
|
||||
type secretFetcher interface {
|
||||
GetSecret(ctx context.Context, secretName string, opts *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error)
|
||||
}
|
||||
|
||||
// Shim provides convenience methods for working with KeyVault.
|
||||
type Shim struct {
|
||||
sf secretFetcher
|
||||
}
|
||||
|
||||
// NewShim constructs a Shim for a KeyVault instance located at the provided url. The azcore.TokenCredential will
|
||||
// only be used during method calls, it is not verified at initialization.
|
||||
func NewShim(vaultURL string, cred azcore.TokenCredential) (*Shim, error) {
|
||||
c, err := azsecrets.NewClient(vaultURL, cred, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create new azsecrets.Client")
|
||||
}
|
||||
|
||||
return &Shim{sf: c}, nil
|
||||
}
|
||||
|
||||
// GetLatestTLSCertificate fetches the latest version of a keyvault certificate and transforms it into a usable tls.Certificate.
|
||||
func (s *Shim) GetLatestTLSCertificate(ctx context.Context, certName string) (tls.Certificate, error) {
|
||||
resp, err := s.sf.GetSecret(ctx, certName, nil)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, errors.Wrap(err, "could not get secret")
|
||||
}
|
||||
|
||||
pemBlocks, err := getPEMBlocks(*resp.Properties.ContentType, *resp.Value)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, errors.Wrap(err, "could not get pem blocks")
|
||||
}
|
||||
|
||||
var (
|
||||
key crypto.PrivateKey
|
||||
leaf *x509.Certificate
|
||||
leafBytes []byte
|
||||
cas [][]byte
|
||||
)
|
||||
|
||||
for _, v := range pemBlocks {
|
||||
switch {
|
||||
case strings.Contains(v.Type, "PRIVATE KEY"):
|
||||
key, err = parsePrivateKey(v.Bytes)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, errors.Wrap(err, "could not parse private key")
|
||||
}
|
||||
case strings.Contains(v.Type, "CERTIFICATE"):
|
||||
c, err := x509.ParseCertificate(v.Bytes)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, errors.Wrap(err, "could not parse certificate")
|
||||
}
|
||||
if !c.IsCA {
|
||||
leaf = c
|
||||
leafBytes = v.Bytes
|
||||
continue
|
||||
}
|
||||
cas = append(cas, v.Bytes)
|
||||
}
|
||||
}
|
||||
|
||||
if leaf == nil {
|
||||
return tls.Certificate{}, errors.New("could not find leaf certificate")
|
||||
}
|
||||
|
||||
return tls.Certificate{
|
||||
PrivateKey: key,
|
||||
Certificate: append([][]byte{leafBytes}, cas...),
|
||||
Leaf: leaf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getPEMBlocks(contentType, payload string) ([]*pem.Block, error) {
|
||||
switch contentType {
|
||||
case pkcs12ContentType:
|
||||
return handlePFXBytes(payload)
|
||||
case pemContentType:
|
||||
return handlePEMBytes(payload)
|
||||
}
|
||||
return nil, errors.Errorf("unsupported content type %s", contentType)
|
||||
}
|
||||
|
||||
func handlePFXBytes(v string) ([]*pem.Block, error) {
|
||||
pfxBytes, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not base64 decode keyvault.SecretBundle.Value")
|
||||
}
|
||||
|
||||
bl, err := pkcs12.ToPEM(pfxBytes, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert pfx to pem")
|
||||
}
|
||||
|
||||
return bl, nil
|
||||
}
|
||||
|
||||
func handlePEMBytes(v string) ([]*pem.Block, error) {
|
||||
pemData := []byte(v)
|
||||
var pemBlocks []*pem.Block
|
||||
for {
|
||||
b, rest := pem.Decode(pemData)
|
||||
if b == nil {
|
||||
break
|
||||
}
|
||||
pemBlocks = append(pemBlocks, b)
|
||||
pemData = rest
|
||||
}
|
||||
|
||||
if len(pemBlocks) == 0 {
|
||||
return nil, errors.New("no pem blocks in input bytes")
|
||||
}
|
||||
|
||||
return pemBlocks, nil
|
||||
}
|
||||
|
||||
// from crypto/tls/tls.go
|
||||
func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
|
||||
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
|
||||
switch key := key.(type) {
|
||||
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
|
||||
return key, nil
|
||||
default:
|
||||
return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping")
|
||||
}
|
||||
}
|
||||
if key, err := x509.ParseECPrivateKey(der); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("tls: failed to parse private key")
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package keyvault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetLatestTLSCert(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
certPath string
|
||||
contentType string
|
||||
}{
|
||||
{
|
||||
name: "pem encoding",
|
||||
certPath: "testdata/dummy.pem",
|
||||
contentType: pemContentType,
|
||||
},
|
||||
{
|
||||
name: "pfx encoding",
|
||||
certPath: "testdata/dummy.pfx",
|
||||
contentType: pkcs12ContentType,
|
||||
},
|
||||
}
|
||||
|
||||
for _, ts := range tests {
|
||||
ts := ts
|
||||
t.Run(ts.name, func(t *testing.T) {
|
||||
kvc := Shim{sf: newFakeSecretFetcher(ts.certPath, ts.contentType)}
|
||||
|
||||
cert, err := kvc.GetLatestTLSCertificate(context.TODO(), "dummy")
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, cert.Leaf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type fakeSecretFetcher struct {
|
||||
certPath string
|
||||
contentType string
|
||||
}
|
||||
|
||||
func newFakeSecretFetcher(certPath, contentType string) *fakeSecretFetcher {
|
||||
return &fakeSecretFetcher{certPath: certPath, contentType: contentType}
|
||||
}
|
||||
|
||||
func (f *fakeSecretFetcher) GetSecret(_ context.Context, _ string, _ *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) {
|
||||
bs, err := os.ReadFile(f.certPath)
|
||||
if err != nil {
|
||||
return azsecrets.GetSecretResponse{}, errors.Wrap(err, "could not read file")
|
||||
}
|
||||
|
||||
v := string(bs)
|
||||
resp := azsecrets.GetSecretResponse{
|
||||
Secret: azsecrets.Secret{
|
||||
Properties: &azsecrets.Properties{ContentType: &f.contentType},
|
||||
Value: &v,
|
||||
},
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDQ15Os/mH6Z7px
|
||||
Zv5ZdRQJyhmO8yzwKuvQi2q99cDQG1B6Kkh1R3rK/xwuIK2Yup5O8aFGOdHJZ3ly
|
||||
9yNdVONyoOQLmvnyhV5vvs1HhdggvcvaT0BFjgTTedCMxUS+cVKXi7NsWUyCgba4
|
||||
8+l9VRrykSenvx6W1wDAWTvuVBb+CU2A7SCsX1IspIY2z0xPnW62OCnAWJ1KAXJD
|
||||
wxByVAoVP1CYUoFg+T/MXSHqDxjVQWZf+cvsQO3gF6/Qj8Zmbt4EYuztchYCa2RP
|
||||
jzEATjPFnISdyfhmksLiAFcs4LSn7RjclASoprTOKpd/3YFMKAv04tqxVa3YZsOt
|
||||
piTxpa2VAgMBAAECggEBAIWo80LMrDhvGsxpdnAalnCNgD4VNLWhQrt9/xsEphqK
|
||||
4L7PQQCOdvBkxcxf7brJ9Xfg/a7MYo/cQcZqlZ+uLMO4ZTtoPIATC5XJL+iOqPyL
|
||||
fGSEREY/1qbiV69AsfaQ4KNNLdAydt0v15oXwWANj+mfLRoXH6S6hiiU895cwzph
|
||||
4X7H8nwx37hZ636Dav/q98vyd9RG5P4b3tv7bQ2kEIMxLEUSLZPwcataxIYBV7OH
|
||||
vUYlXaO1qn7Er6XYU92bV4tZY2mpmFx2Sear2XG/Q+9yP/Bu668L+kO+/SpNa9Md
|
||||
wPWV1vt+kRSNHgQJsVW5IDQMFAOdhqOaWnn66G2ErSECgYEA8rkU+XxO/pfkfm2n
|
||||
CS7AMZhym4R7EQ+nQw3CmOvbOoBfLkU8nUGO1ODA7EjKRIyCvB8n6Mm74v81H5/+
|
||||
zjl3Cb5z6E57hcrxM9lw+7voXi3Y7+iSKbqkAlGM7c8JLtJaOgZrt+BUSUMmJrKN
|
||||
U2MFy69VMEyB0TvCU14WF8v+Oh8CgYEA3EQNK+YQcvaW5QSvHPlgJUubP5TJhVyT
|
||||
Ip1OSnORrNI9pB4RoIJA7eZoTp/qK+YeHe/yPIfQyL1YDPnhVAgoSHDMT0JCSBxv
|
||||
dRT3StCG54UsBo5b0QcYP0Wj5Ciayx3T+xZGTPg8eAYSiTVdhnAHHausYJ49Hnj+
|
||||
GMlN7MWticsCgYEAwlfDHYeU0HDZ+QjfJ5ERPiSsDy1iRGTeLehEmaCvZgYHL8ss
|
||||
H1WwgW57yjT2DzDaNLpVgCSWlch1xp6arJCCaYDe2XCNorC9tCA0QLtR8KaQ/naf
|
||||
IV5Zl6moR3jwB1dR+wfNE+tAUXC8iVuJoOy2ZUI72XJItzk7/PhmhCNHqU0CgYEA
|
||||
rW87+8vcOdlmOQ/2HldRWCxvIqIyBzs8c23vXnofQzgL5zTx5jOJkojwqrAJ/+Ti
|
||||
4+myD+1U/SrxsM30mWkO5vNCPEpMzGDvdf47NYJ6JsRaRRNEwpLWicN458b9E1/6
|
||||
MON6GVMAsfT+FWGasad2QuuRAEa4k0zrrnKbVArWuP8CgYEAun2ft8oybIQKuo0d
|
||||
Hht5q/1gebBtYKIplgxGiAzlnuFlGzR8SFmUXnVJz7I4+4OndMd02TFDv4PX8fj3
|
||||
7X2wtxeYtaWRBpfg/vz+Z6QMwxF0Sx9uRI9r05QntGec4ovg8rC9cr9WpBpwVmQf
|
||||
gEkXqk6ZftIs56PG1z3QhlK1mbs=
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDQDCCAiigAwIBAgIQbkrr03sSQSShhzE0Zkk18zANBgkqhkiG9w0BAQsFADAS
|
||||
MRAwDgYDVQQDEwdmb28uY29tMB4XDTIyMDQwNzE1NTg1OVoXDTIzMDQwNzE2MDg1
|
||||
OVowEjEQMA4GA1UEAxMHZm9vLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
|
||||
AQoCggEBANDXk6z+YfpnunFm/ll1FAnKGY7zLPAq69CLar31wNAbUHoqSHVHesr/
|
||||
HC4grZi6nk7xoUY50clneXL3I11U43Kg5Aua+fKFXm++zUeF2CC9y9pPQEWOBNN5
|
||||
0IzFRL5xUpeLs2xZTIKBtrjz6X1VGvKRJ6e/HpbXAMBZO+5UFv4JTYDtIKxfUiyk
|
||||
hjbPTE+dbrY4KcBYnUoBckPDEHJUChU/UJhSgWD5P8xdIeoPGNVBZl/5y+xA7eAX
|
||||
r9CPxmZu3gRi7O1yFgJrZE+PMQBOM8WchJ3J+GaSwuIAVyzgtKftGNyUBKimtM4q
|
||||
l3/dgUwoC/Ti2rFVrdhmw62mJPGlrZUCAwEAAaOBkTCBjjAOBgNVHQ8BAf8EBAMC
|
||||
BaAwCQYDVR0TBAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYD
|
||||
VR0RBAswCYIHZm9vLmNvbTAfBgNVHSMEGDAWgBT+rzOo+xdTZuRNNeo2U4/FAJDR
|
||||
PDAdBgNVHQ4EFgQU/q8zqPsXU2bkTTXqNlOPxQCQ0TwwDQYJKoZIhvcNAQELBQAD
|
||||
ggEBAB8tusBVjb+bksPrWZk/cNlYX2sqGjRHt4AZ5SW+VDhkLYlOpBENgDfkB5tS
|
||||
Oz0WEv95wbWR/mzQv3AmhToPx/ZOUYWq+v1CBX6amkgPkf/5VLpk4fveVnkGLLhS
|
||||
23/LeMZzPdVlKTsTYC1z0R2a/2Bd5XKnLT00cf3M1Gj/Ul3O4LmNDAY0KIohYljv
|
||||
xje8Auhg1kbib7XbKr1KfPZztu4a0NmKvFqVgUooUOX9vFT1aF1GwENDezMf/NyJ
|
||||
YB8x7dEOwYg8UBMA3PIOdobf0UKkVgnPh+kqKHL1/2ctYnNZvZkZ5f55+Ac0wTKm
|
||||
6162fxN8Ri8B4A2YuGiWM+4MFPw=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,8 @@
|
|||
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
|
Загрузка…
Ссылка в новой задаче