diff --git a/cert/cert.go b/cert/cert.go new file mode 100644 index 000000000..564d5fa92 --- /dev/null +++ b/cert/cert.go @@ -0,0 +1,51 @@ +package cert + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" +) + +var ( + pemStart = []byte("-----BEGIN ") + certBlockType = "CERTIFICATE" +) + +// DecodeCertificates accepts a byte slice of data and decodes it into a slice of Certificates +// if certificates can't be read or they are invalid, an error is returned +// Taken from https://github.com/zakjan/cert-chain-resolver/blob/master/certUtil/io.go +func DecodeCertificates(data []byte) ([]*x509.Certificate, error) { + if isPEM := bytes.HasPrefix(data, pemStart); isPEM { + var certs []*x509.Certificate + + for len(data) > 0 { + var block *pem.Block + + block, data = pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("a valid block wasn't found in byte data") + } + if block.Type != certBlockType { + return nil, fmt.Errorf("invalid block type for detected PEM cert") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, errors.New("invalid certificate") + } + + certs = append(certs, cert) + } + + return certs, nil + } + + certs, err := x509.ParseCertificates(data) + if err != nil { + return nil, errors.New("invalid certificate") + } + + return certs, nil +} diff --git a/cert/cert_test.go b/cert/cert_test.go new file mode 100644 index 000000000..502b6c2d2 --- /dev/null +++ b/cert/cert_test.go @@ -0,0 +1,33 @@ +package cert + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecodeCertificates(t *testing.T) { + singleCert := `-----BEGIN CERTIFICATE----- +MIIC1TCCAb2gAwIBAgIRAIJmUfoLsdqXVQqzT1CTMNUwDQYJKoZIhvcNAQELBQAw +ADAeFw0yMjAxMTcxNDIzNDVaFw0yMjA0MTcxNDIzNDVaMAAwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC/MvDaXxSxdO3K3L5PY/OP9Ol7juwnPtOi651R +J7S3r2FTmZB6zUMRJG0oGFjfCQheXZJQkxURmSfdW/tkzRWl4Bme8xh4kFNdi/3t +ddCE2ckNvp9UCaxT8baRiG+xT/7TAONK8XoDLIyH2/zpprtVE0xo38VmWYmmgpNM +VEf87SXCSkO/fGW6Pt1qUwu47I4/5jQRh9B+SJQwmmyvR55RQ1Z9otCwzNgOteV0 +0Jn39fgCkavEIwsUwyV6hE2zjpl0uTkw93cHbn2mJY6sAElLeRZYm2Xo/2Jt0BOZ ++3pfV/yHaXLg+/eZYHE7wcYcLGCsjFbM43PLAhr8mUR93Y0FAgMBAAGjSjBIMA4G +A1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMCgGA1UdEQEB/wQeMByCC3RvZG9t +dmMuY29tgg1teXRvZG9tdmMuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQCZuXvIrx/a +pdvC2ACppazqtvE+WA4EZZlxFgk3zCgkhNBFIFfAJq5F5uGLAzhgrnxvcYk2kfqx +Ne/uCskl5en2gcd0zNyyJxPLUI4nCSlNje8RK9k80mlYh5GOeFUSmKgx2afn0dYI +aLWEgNOHbxJM+mEBGyLL0z9ps5ypxin6BfjyDy6rfXZHINGXbIpfaURuYhawMteW +MsetexKIFgJdt0J62XJvPuQpj58mSLZaDLf1lAtdVssg6Kl3Ev3EXzEaOYm2Xgef +hxKR99RwftrUXUWusQa/jjUB2JQYh0g3c9L4FoCRiLt2mYL/8JM8ihNqGheu+IGx +0Z7hvxeupgPG +-----END CERTIFICATE-----` + + certs, err := DecodeCertificates([]byte(singleCert)) + assert.Nil(t, err) + assert.Len(t, certs, 1, "expected single certificate") + assert.Len(t, certs[0].DNSNames, 2, "expected 2 DNS names") +} diff --git a/controllers/envoyfleet_resources.go b/controllers/envoyfleet_resources.go index 28fac63cb..51c2e4b41 100644 --- a/controllers/envoyfleet_resources.go +++ b/controllers/envoyfleet_resources.go @@ -219,7 +219,6 @@ func (e *EnvoyFleetResources) generateDeployment() { }, }, } - return } func (e *EnvoyFleetResources) generateService() { diff --git a/development/certs/.gitignore b/development/certs/.gitignore new file mode 100644 index 000000000..5934c043a --- /dev/null +++ b/development/certs/.gitignore @@ -0,0 +1,12 @@ +dh +*.der +*.clr +*.crl +*.crt +*.csr +*.key +*.p12 +*.pem +*.srl +index* +serial* diff --git a/development/certs/Makefile b/development/certs/Makefile new file mode 100644 index 000000000..6c42b57b1 --- /dev/null +++ b/development/certs/Makefile @@ -0,0 +1,153 @@ +###################################################################### +# Most of these commands are borrowed from FreeRADIUS certs management (/etc/freeradius/certs) +# +###################################################################### + +DH_KEY_SIZE = 2048 +OPENSSL = openssl +EXTERNAL_CA = $(wildcard external_ca.*) + +ifneq "$(EXTERNAL_CA)" "" +PARTIAL = -partial_chain +endif + +# +# Set the passwords +# +include passwords.mk + +###################################################################### +# +# Make the necessary files, but not client certificates. +# +###################################################################### +.PHONY: all +all: index.txt serial dh ca server + +.PHONY: client +client: client.pem + +.PHONY: ca +ca: ca.der ca.crl + +.PHONY: server +server: server.pem server.vrfy + +.PHONY: verify +verify: server.vrfy client.vrfy + +passwords.mk: server.cnf ca.cnf client.cnf + @echo "PASSWORD_SERVER = '$(shell grep output_password server.cnf | sed 's/.*=//;s/^ *//')'" > $@ + @echo "PASSWORD_CA = '$(shell grep output_password ca.cnf | sed 's/.*=//;s/^ *//')'" >> $@ + @echo "PASSWORD_CLIENT = '$(shell grep output_password client.cnf | sed 's/.*=//;s/^ *//')'" >> $@ + @echo "USER_NAME = '$(shell grep emailAddress client.cnf | grep '@' | sed 's/.*=//;s/^ *//')'" >> $@ + @echo "CA_DEFAULT_DAYS = '$(shell grep default_days ca.cnf | sed 's/.*=//;s/^ *//')'" >> $@ + +###################################################################### +# +# Diffie-Hellman parameters +# +###################################################################### +dh: + $(OPENSSL) dhparam -out dh -2 $(DH_KEY_SIZE) + +###################################################################### +# +# Create a new self-signed CA certificate +# +###################################################################### +ca.key ca.pem: ca.cnf + @[ -f index.txt ] || $(MAKE) index.txt + @[ -f serial ] || $(MAKE) serial + $(OPENSSL) req -new -x509 -keyout ca.key -out ca.pem \ + -days $(CA_DEFAULT_DAYS) -config ./ca.cnf \ + -passin pass:$(PASSWORD_CA) -passout pass:$(PASSWORD_CA) + chmod g+r ca.key + +ca.der: ca.pem + $(OPENSSL) x509 -inform PEM -outform DER -in ca.pem -out ca.der + +ca.crl: ca.pem + $(OPENSSL) ca -gencrl -keyfile ca.key -cert ca.pem -config ./ca.cnf -out ca-crl.pem -key $(PASSWORD_CA) + $(OPENSSL) crl -in ca-crl.pem -outform der -out ca.crl + rm ca-crl.pem + +###################################################################### +# +# Create a new server certificate, signed by the above CA. +# +###################################################################### +# NOTE '-nodes' means: do not encrypt the server.key +# Remove if need it encrypted +server.csr server.key: server.cnf + $(OPENSSL) req -new -nodes -out server.csr -keyout server.key -config ./server.cnf + chmod g+r server.key + +server.crt: server.csr ca.key ca.pem + $(OPENSSL) ca -batch -extensions x509_extensions -extfile ./server.cnf -keyfile ca.key -cert ca.pem -in server.csr -key $(PASSWORD_CA) -out server.crt -config ./server.cnf + +server.p12: server.crt + $(OPENSSL) pkcs12 -export -in server.crt -inkey server.key -out server.p12 -passin pass:$(PASSWORD_SERVER) -passout pass:$(PASSWORD_SERVER) + chmod g+r server.p12 + +server.pem: server.p12 + $(OPENSSL) pkcs12 -in server.p12 -out server.pem -passin pass:$(PASSWORD_SERVER) -passout pass:$(PASSWORD_SERVER) + chmod g+r server.pem + +.PHONY: server.vrfy +server.vrfy: ca.pem + @$(OPENSSL) verify $(PARTIAL) -CAfile ca.pem server.pem + + +###################################################################### +# +# Create a new client certificate, signed by the the above CA certificate. +# +###################################################################### +client.csr client.key: client.cnf + $(OPENSSL) req -new -out client.csr -keyout client.key -config ./client.cnf + chmod g+r client.key + +client.crt: client.csr ca.pem ca.key + $(OPENSSL) ca -batch -keyfile ca.key -cert ca.pem -in client.csr -key $(PASSWORD_CA) -out client.crt -config ./client.cnf + +client.p12: client.crt + $(OPENSSL) pkcs12 -export -in client.crt -inkey client.key -out client.p12 -passin pass:$(PASSWORD_CLIENT) -passout pass:$(PASSWORD_CLIENT) + chmod g+r client.p12 + +client.pem: client.p12 + $(OPENSSL) pkcs12 -in client.p12 -out client.pem -passin pass:$(PASSWORD_CLIENT) -passout pass:$(PASSWORD_CLIENT) + chmod g+r client.pem + cp client.pem $(USER_NAME).pem + +.PHONY: client.vrfy +client.vrfy: ca.pem client.pem + $(OPENSSL) verify -CAfile ca.pem client.pem + + +###################################################################### +# +# Miscellaneous rules. +# +###################################################################### +index.txt: + @touch index.txt + +serial: + @echo '01' > serial + +print: + $(OPENSSL) x509 -text -in server.crt + +printca: + $(OPENSSL) x509 -text -in ca.pem + +clean: + @rm -f *~ *old server.csr server.key server.crt server.p12 server.pem client.csr client.key client.crt client.p12 client.pem + +# +# Make a target that people won't run too often. +# +destroycerts: + rm -f *~ dh *.csr *.crt *.p12 *.der *.pem *.key index.txt* \ + serial* *\.0 *\.1 ca-crl.pem ca.crl diff --git a/development/certs/README.md b/development/certs/README.md new file mode 100644 index 000000000..5f3e397b0 --- /dev/null +++ b/development/certs/README.md @@ -0,0 +1,101 @@ +# TLS certificates testing + +This directory contains files for the generation of self-signed certificates and testing Envoys TLS. + +The order of testing is following: + +1. Generate CA and Intermediate CA, server certificate from CA (usual cert) and server certificate from the Intermediate. + + ```shell + make destroycerts + make all + cd intermediate + make destroycerts + make all + ``` + + The resulting files will be: + + certs/ca.pem - Root CA certificate. + + certs/intermediate/ca.pem - the Intermediate CA, signed by Root CA. + + certs/server.pem, certs/server.key - the certificate and the key for domains www.example.com, second.example.com. + + certs/intermediate/server.pem, certs/intermediate/server.key - the certificate and the key signed by the Intermediate CA for domains www.example2.com, second.example2.com, localhost, localhost.localdomain, 127.0.0.1. + +2. Prepare and deploy Kubernetes secrets with such certificates. + + In certs, prepare and deploy the certificate, signed by Root CA (www.example.com): + + ```shell + make print + ``` + + Select and Copy the certificate part in the bottom beginning with ---BEGIN CERTIFICATE --- and ending with -- END CERTIFICATE --- including these lines. + Save these lines into s.pem file. + + Create the kubernetes secret in default namespace. + + ```shell + kubectl create secret tls server-cert --cert=s.pem --key=server.key + ``` + + Second part - prepare and deploy the certificate, signed by the Intermediate CA. + + NOTE: All work in this section should be done in the intermediate directory. + + ```shell + cd intermediate + make print + ``` + + Select and Copy the certificate part in the bottom beginning with ---BEGIN CERTIFICATE --- and ending with -- END CERTIFICATE --- including these lines. + Save these lines into s.pem file. + + Prepend the Intermediate CA certificate to the end of this file, forming the certifite chain: + + ```shell + cat ca.pem >> s.pem + ``` + + Create the kubernetes secret in default namespace. + + ```shell + kubectl create secret tls server-int-cert --cert=s.pem --key=server.key + ``` + +3. Deplying EnvoyFleet TLS configuration + + Prepare EnvoyFleet file (see config/samples/) that has the following lines in the end: + + ```yaml + ... + tls: + cipherSuites: + - ECDHE-ECDSA-AES128-SHA + - ECDHE-RSA-AES128-SHA + - AES128-GCM-SHA256 + tlsMinimumProtocolVersion: TLSv1_2 + tlsMaximumProtocolVersion: TLSv1_3 + tlsSecrets: + - secretRef: server-int-cert + namespace: default + - secretRef: server-cert + namespace: default + ``` + + Deploy the file, create the port-forwarding to 19000 port of Envoy and verify that listener configuration was updated with the additional 2 filter_chain_matche with different hostnames in the filter. + +4. Testing + + Test that server cert works verifying the certificates with main CA. + The intermediate certificate should work since Envoy supplies the Intermediate CA cert as a second entry in the certificate. + + Curl should connect successfully. + + ```shell + export EXTERNAL_IP= + curl --resolve www.example.com:443:$EXTERNAL_IP https://www.example.com/ --cacert development/certs/ca.pem -v + curl --resolve www.example2.com:443:$EXTERNAL_IP https://www.example2.com/ --cacert development/certs/ca.pem -v + ``` diff --git a/development/certs/ca.cnf b/development/certs/ca.cnf new file mode 100644 index 000000000..6b49a11e4 --- /dev/null +++ b/development/certs/ca.cnf @@ -0,0 +1,70 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./ +certs = $dir +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/ca.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/ca.key +RANDFILE = $dir/.rand +name_opt = ca_default +cert_opt = ca_default +default_days = 60 +default_crl_days = 30 +default_md = sha256 +preserve = no +policy = policy_match +crlDistributionPoints = URI:http://www.example.org/example_ca.crl + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +prompt = no +distinguished_name = certificate_authority +default_bits = 2048 +input_password = whatever +output_password = whatever +x509_extensions = v3_ca + +[certificate_authority] +countryName = FR +stateOrProvinceName = Example +localityName = Somewhere +organizationName = Example Inc. +emailAddress = admin@example.org +commonName = "Example Certificate Authority" + +[v3_ca] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +basicConstraints = critical,CA:true +crlDistributionPoints = URI:http://www.example.org/example_ca.crl + +# For the intermediate CA signing +[intermediate_v3_ca] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +basicConstraints = critical,CA:true,pathlen:0 +crlDistributionPoints = URI:http://www.example.org/example_ca.crl +authorityKeyIdentifier = keyid:always +issuerAltName = issuer:copy diff --git a/development/certs/client.cnf b/development/certs/client.cnf new file mode 100644 index 000000000..9aa6132c0 --- /dev/null +++ b/development/certs/client.cnf @@ -0,0 +1,53 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./ +certs = $dir +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/ca.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/ca.key +RANDFILE = $dir/.rand +name_opt = ca_default +cert_opt = ca_default +default_days = 60 +default_crl_days = 30 +default_md = sha256 +preserve = no +policy = policy_match + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +prompt = no +distinguished_name = client +default_bits = 2048 +input_password = whatever +output_password = whatever + +[client] +countryName = FR +stateOrProvinceName = Example +localityName = Somewhere +organizationName = Example Inc. +emailAddress = user@example.org +commonName = user@example.org diff --git a/development/certs/intermediate/.gitignore b/development/certs/intermediate/.gitignore new file mode 100644 index 000000000..f04134b57 --- /dev/null +++ b/development/certs/intermediate/.gitignore @@ -0,0 +1,12 @@ +dh +*.der +*.clr +*.crl +*.crt +*.csr +*.srl +*.key +*.p12 +*.pem +index* +serial* diff --git a/development/certs/intermediate/Makefile b/development/certs/intermediate/Makefile new file mode 100644 index 000000000..b9e8ae756 --- /dev/null +++ b/development/certs/intermediate/Makefile @@ -0,0 +1,158 @@ +###################################################################### +# Most of these commands are borrowed from FreeRADIUS certs management (/etc/freeradius/certs) +# +###################################################################### +SHELL = bash +DH_KEY_SIZE = 2048 +OPENSSL = openssl +EXTERNAL_CA = $(wildcard external_ca.*) + +ifneq "$(EXTERNAL_CA)" "" +PARTIAL = -partial_chain +endif + +# +# Set the passwords +# +include passwords.mk + +###################################################################### +# +# Make the necessary files, but not client certificates. +# +###################################################################### +.PHONY: all +all: index.txt serial dh ca server + +.PHONY: client +client: client.pem + +.PHONY: ca +ca: ca.der ca.crl + +.PHONY: server +server: server.pem server.vrfy + +.PHONY: verify +verify: server.vrfy client.vrfy + +passwords.mk: server.cnf ca.cnf client.cnf + @echo "PASSWORD_SERVER = '$(shell grep output_password server.cnf | sed 's/.*=//;s/^ *//')'" > $@ + @echo "PASSWORD_CA = '$(shell grep output_password ca.cnf | sed 's/.*=//;s/^ *//')'" >> $@ + @echo "PASSWORD_CLIENT = '$(shell grep output_password client.cnf | sed 's/.*=//;s/^ *//')'" >> $@ + @echo "USER_NAME = '$(shell grep emailAddress client.cnf | grep '@' | sed 's/.*=//;s/^ *//')'" >> $@ + @echo "CA_DEFAULT_DAYS = '$(shell grep default_days ca.cnf | sed 's/.*=//;s/^ *//')'" >> $@ + +###################################################################### +# +# Diffie-Hellman parameters +# +###################################################################### +dh: + $(OPENSSL) dhparam -out dh -2 $(DH_KEY_SIZE) + +###################################################################### +# +# Create a intermediate self-signed CA certificate using the CA above +# in the directory structure. +# +###################################################################### +ca.key ca.csr ca.pem: ca.cnf + @[ -f index.txt ] || $(MAKE) index.txt + @[ -f serial ] || $(MAKE) serial + $(OPENSSL) req -new -nodes -keyout ca.key -out ca.csr -config ./ca.cnf + chmod g+r ca.key + # Sign the cert with the root CA about + pushd .. &&\ + $(OPENSSL) x509 -req -in intermediate/ca.csr -out intermediate/ca.pem \ + -days $(CA_DEFAULT_DAYS) -extensions intermediate_v3_ca -extfile ./ca.cnf -CAcreateserial -CA ca.pem -CAkey ca.key\ + -passin pass:$(PASSWORD_CA) &&\ + popd + +ca.der: ca.pem + $(OPENSSL) x509 -inform PEM -outform DER -in ca.pem -out ca.der + +ca.crl: ca.pem + $(OPENSSL) ca -gencrl -keyfile ca.key -cert ca.pem -config ./ca.cnf -out ca-crl.pem -key $(PASSWORD_CA) + $(OPENSSL) crl -in ca-crl.pem -outform der -out ca.crl + rm ca-crl.pem + +###################################################################### +# +# Create a new server certificate, signed by the above CA. +# +###################################################################### +# NOTE '-nodes' means: do not encrypt the server.key +# Remove if need it encrypted +server.csr server.key: server.cnf + $(OPENSSL) req -new -nodes -out server.csr -keyout server.key -config ./server.cnf + chmod g+r server.key + +server.crt: server.csr ca.key ca.pem + $(OPENSSL) ca -batch -extensions x509_extensions -extfile ./server.cnf -keyfile ca.key -cert ca.pem -in server.csr -key $(PASSWORD_CA) -out server.crt -config ./server.cnf + +server.p12: server.crt + $(OPENSSL) pkcs12 -export -in server.crt -inkey server.key -out server.p12 -passin pass:$(PASSWORD_SERVER) -passout pass:$(PASSWORD_SERVER) + chmod g+r server.p12 + +server.pem: server.p12 + $(OPENSSL) pkcs12 -in server.p12 -out server.pem -passin pass:$(PASSWORD_SERVER) -passout pass:$(PASSWORD_SERVER) + chmod g+r server.pem + +.PHONY: server.vrfy +server.vrfy: ca.pem + @$(OPENSSL) verify $(PARTIAL) -CAfile ../ca.pem -untrusted ca.pem server.pem + + +###################################################################### +# +# Create a new client certificate, signed by the the above CA certificate. +# +###################################################################### +client.csr client.key: client.cnf + $(OPENSSL) req -new -out client.csr -keyout client.key -config ./client.cnf + chmod g+r client.key + +client.crt: client.csr ca.pem ca.key + $(OPENSSL) ca -batch -keyfile ca.key -cert ca.pem -in client.csr -key $(PASSWORD_CA) -out client.crt -config ./client.cnf + +client.p12: client.crt + $(OPENSSL) pkcs12 -export -in client.crt -inkey client.key -out client.p12 -passin pass:$(PASSWORD_CLIENT) -passout pass:$(PASSWORD_CLIENT) + chmod g+r client.p12 + +client.pem: client.p12 + $(OPENSSL) pkcs12 -in client.p12 -out client.pem -passin pass:$(PASSWORD_CLIENT) -passout pass:$(PASSWORD_CLIENT) + chmod g+r client.pem + cp client.pem $(USER_NAME).pem + +.PHONY: client.vrfy +client.vrfy: ca.pem client.pem + $(OPENSSL) verify -CAfile ../ca.pem -untrusted ca.pem client.pem + + +###################################################################### +# +# Miscellaneous rules. +# +###################################################################### +index.txt: + @touch index.txt + +serial: + @echo '01' > serial + +print: + $(OPENSSL) x509 -text -in server.crt + +printca: + $(OPENSSL) x509 -text -in ca.pem + +clean: + @rm -f *~ *old server.csr server.key server.crt server.p12 server.pem client.csr client.key client.crt client.p12 client.pem + +# +# Make a target that people won't run too often. +# +destroycerts: + rm -f *~ dh *.csr *.crt *.p12 *.der *.pem *.key index.txt* \ + serial* *\.0 *\.1 ca-crl.pem ca.crl diff --git a/development/certs/intermediate/ca.cnf b/development/certs/intermediate/ca.cnf new file mode 100644 index 000000000..203ad2b58 --- /dev/null +++ b/development/certs/intermediate/ca.cnf @@ -0,0 +1,62 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./ +certs = $dir +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/ca.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/ca.key +RANDFILE = $dir/.rand +name_opt = ca_default +cert_opt = ca_default +default_days = 60 +default_crl_days = 30 +default_md = sha256 +preserve = no +policy = policy_anything +crlDistributionPoints = URI:http://www.example.org/example_ca.crl + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +prompt = no +distinguished_name = intermediate_certificate_authority +default_bits = 2048 +input_password = whatever +output_password = whatever +x509_extensions = v3_ca + +[intermediate_certificate_authority] +countryName = FR +stateOrProvinceName = Example +localityName = Somewhere +organizationName = Example Inc. +emailAddress = admin@example.org +commonName = "Example Intermediate Certificate Authority #1" + +[v3_ca] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +basicConstraints = critical,CA:true,pathlen:0 +crlDistributionPoints = URI:http://www.example.org/example_ca.crl + diff --git a/development/certs/intermediate/client.cnf b/development/certs/intermediate/client.cnf new file mode 100644 index 000000000..9aa6132c0 --- /dev/null +++ b/development/certs/intermediate/client.cnf @@ -0,0 +1,53 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./ +certs = $dir +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/ca.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/ca.key +RANDFILE = $dir/.rand +name_opt = ca_default +cert_opt = ca_default +default_days = 60 +default_crl_days = 30 +default_md = sha256 +preserve = no +policy = policy_match + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +prompt = no +distinguished_name = client +default_bits = 2048 +input_password = whatever +output_password = whatever + +[client] +countryName = FR +stateOrProvinceName = Example +localityName = Somewhere +organizationName = Example Inc. +emailAddress = user@example.org +commonName = user@example.org diff --git a/development/certs/intermediate/passwords.mk b/development/certs/intermediate/passwords.mk new file mode 100644 index 000000000..a875e4aaf --- /dev/null +++ b/development/certs/intermediate/passwords.mk @@ -0,0 +1,5 @@ +PASSWORD_SERVER = 'whatever' +PASSWORD_CA = 'whatever' +PASSWORD_CLIENT = 'whatever' +USER_NAME = 'user@example.org' +CA_DEFAULT_DAYS = '60' diff --git a/development/certs/intermediate/server.cnf b/development/certs/intermediate/server.cnf new file mode 100644 index 000000000..28c57a0fe --- /dev/null +++ b/development/certs/intermediate/server.cnf @@ -0,0 +1,83 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./ +certs = $dir +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/server.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/server.key +RANDFILE = $dir/.rand +name_opt = ca_default +cert_opt = ca_default +default_days = 60 +default_crl_days = 30 +default_md = sha256 +preserve = no +policy = policy_anything + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +prompt = no +distinguished_name = server +default_bits = 2048 +input_password = whatever +output_password = whatever +req_extensions = v3_req +x509_extensions = x509_extensions + +[server] +countryName = FR +stateOrProvinceName = Example +localityName = Somewhere +organizationName = Example Inc. +emailAddress = admin@example.org +# Use a friendly name here because its presented to the user. The server's DNS +# names are placed in Subject Alternate Names. Plus, DNS names here is deprecated +# by both IETF and CA/Browser Forums. If you place a DNS name here, then you +# must include the DNS name in the SAN too (otherwise, Chrome and others that +# strictly follow the CA/Browser Baseline Requirements will fail). +commonName = "www.example2.com" + +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[ x509_extensions ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names +# RFC 5280, Section 4.2.1.12 makes EKU optional +# CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused +# In either case, you probably only need serverAuth. +# extendedKeyUsage = serverAuth, clientAuth + +[alt_names] +DNS.1 = www.example2.com +DNS.2 = second2.example.com +DNS.3 = localhost +DNS.4 = localhost.localdomain +DNS.5 = 127.0.0.1 diff --git a/development/certs/passwords.mk b/development/certs/passwords.mk new file mode 100644 index 000000000..a875e4aaf --- /dev/null +++ b/development/certs/passwords.mk @@ -0,0 +1,5 @@ +PASSWORD_SERVER = 'whatever' +PASSWORD_CA = 'whatever' +PASSWORD_CLIENT = 'whatever' +USER_NAME = 'user@example.org' +CA_DEFAULT_DAYS = '60' diff --git a/development/certs/server.cnf b/development/certs/server.cnf new file mode 100644 index 000000000..96bd77a44 --- /dev/null +++ b/development/certs/server.cnf @@ -0,0 +1,80 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./ +certs = $dir +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/server.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/server.key +RANDFILE = $dir/.rand +name_opt = ca_default +cert_opt = ca_default +default_days = 60 +default_crl_days = 30 +default_md = sha256 +preserve = no +policy = policy_match + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +prompt = no +distinguished_name = server +default_bits = 2048 +input_password = whatever +output_password = whatever +req_extensions = v3_req +x509_extensions = x509_extensions + +[server] +countryName = FR +stateOrProvinceName = Example +localityName = Somewhere +organizationName = Example Inc. +emailAddress = admin@example.org +# Use a friendly name here because its presented to the user. The server's DNS +# names are placed in Subject Alternate Names. Plus, DNS names here is deprecated +# by both IETF and CA/Browser Forums. If you place a DNS name here, then you +# must include the DNS name in the SAN too (otherwise, Chrome and others that +# strictly follow the CA/Browser Baseline Requirements will fail). +commonName = "www.example.com" + +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[ x509_extensions ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names +# RFC 5280, Section 4.2.1.12 makes EKU optional +# CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused +# In either case, you probably only need serverAuth. +# extendedKeyUsage = serverAuth, clientAuth + +[alt_names] +DNS.1 = www.example.com +DNS.2 = second.example.com diff --git a/envoy/config/listener.go b/envoy/config/listener.go index b172bd627..41c40300b 100644 --- a/envoy/config/listener.go +++ b/envoy/config/listener.go @@ -12,6 +12,8 @@ import ( tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" "github.com/envoyproxy/go-control-plane/pkg/wellknown" "google.golang.org/protobuf/types/known/anypb" + + "github.com/kubeshop/kusk-gateway/cert" ) const ( @@ -68,61 +70,60 @@ type Certificate struct { Key string } -// AddHTTPManagerFilterChains inserts HTTP Manager as the listener filter chain(s) -// If certificates are present an additional TLS-enabled filter chain is added and protocol type detection is enabled with TLS Inspector Listener filter. -func (l *listenerBuilder) AddHTTPManagerFilterChains(httpConnectionManager *hcm.HttpConnectionManager, tlsConfig TLS) error { - anyHTTPManagerConfig, err := anypb.New(httpConnectionManager) - if err != nil { - return fmt.Errorf("failed to add http manager to the filter chain: cannot convert to Any message type: %w", err) - } - hcmFilter := &listener.Filter{ - Name: wellknown.HTTPConnectionManager, - ConfigType: &listener.Filter_TypedConfig{TypedConfig: anyHTTPManagerConfig}, - } - // Plain HTTP manager filter chain - hcmPlainChain := &listener.FilterChain{ - Filters: []*listener.Filter{hcmFilter}, +func makeHTTPSFilterChain( + certificate Certificate, + hosts []string, + tlsParams *tls.TlsParameters, + anyHttpConnectionManager *anypb.Any, +) (*listener.FilterChain, error) { + tlsCert := &tls.TlsCertificate{ + CertificateChain: &core.DataSource{ + Specifier: &core.DataSource_InlineString{InlineString: certificate.Cert}, + }, + PrivateKey: &core.DataSource{ + Specifier: &core.DataSource_InlineString{InlineString: certificate.Key}, + }, } - l.addListenerFilterChain(hcmPlainChain) - if len(tlsConfig.Certificates) == 0 { - return nil + if err := tlsCert.Validate(); err != nil { + return nil, fmt.Errorf("invalid tls certificate: %w", err) } - // When certificates are present, we add an additional Listener filter chain that is selected when the connection protocol type is tls. - // HTTP Manager configuration is the same. - // Enable TLS Inspector in the Listener to detect plain http or tls requests. - l.addListenerFilter(&listener.ListenerFilter{Name: wellknown.TLSInspector}) - - // Make sure plain http manager filter chain is selected when protocol type is raw_buffer (not tls). - hcmPlainChain.FilterChainMatch = &listener.FilterChainMatch{TransportProtocol: "raw_buffer"} + tlsDownstreamContext := &tls.DownstreamTlsContext{ + CommonTlsContext: &tls.CommonTlsContext{ + TlsCertificates: []*tls.TlsCertificate{tlsCert}, + TlsParams: tlsParams, + }, + } - // Secure (TLS) HTTP manager filter chain. - // Selected when the connection type is tls. - hcmSecureChain := &listener.FilterChain{ - FilterChainMatch: &listener.FilterChainMatch{TransportProtocol: "tls"}, - Filters: []*listener.Filter{hcmFilter}, + if err := tlsDownstreamContext.Validate(); err != nil { + return nil, fmt.Errorf("invalid tls downstream context: %w", err) } - tlsCerts := make([]*tls.TlsCertificate, len(tlsConfig.Certificates)) - for _, cert := range tlsConfig.Certificates { + anyTls, err := anypb.New(tlsDownstreamContext) + if err != nil { + return nil, fmt.Errorf("unable to marshal TLS config to typed struct: %w", err) + } - tlsCert := &tls.TlsCertificate{ - CertificateChain: &core.DataSource{ - Specifier: &core.DataSource_InlineString{InlineString: cert.Cert}, - }, - PrivateKey: &core.DataSource{ - Specifier: &core.DataSource_InlineString{InlineString: cert.Key}, + return &listener.FilterChain{ + FilterChainMatch: &listener.FilterChainMatch{ + TransportProtocol: "tls", + ServerNames: hosts, + }, + Filters: []*listener.Filter{ + { + Name: wellknown.HTTPConnectionManager, + ConfigType: &listener.Filter_TypedConfig{TypedConfig: anyHttpConnectionManager}, }, - } - - if err := tlsCert.Validate(); err != nil { - return fmt.Errorf("invalid tls certificate: %w", err) - } - - tlsCerts = append(tlsCerts, tlsCert) - } + }, + TransportSocket: &core.TransportSocket{ + Name: wellknown.TransportSocketTLS, + ConfigType: &core.TransportSocket_TypedConfig{TypedConfig: anyTls}, + }, + }, nil +} +func getTLSParameters(tlsConfig TLS) (*tls.TlsParameters, error) { tlsParams := &tls.TlsParameters{} if len(tlsConfig.CipherSuites) > 0 { @@ -132,7 +133,7 @@ func (l *listenerBuilder) AddHTTPManagerFilterChains(httpConnectionManager *hcm. if tlsConfig.TlsMinimumProtocolVersion != "" { tlsProtocolValue, ok := tls.TlsParameters_TlsProtocol_value[tlsConfig.TlsMinimumProtocolVersion] if !ok { - return fmt.Errorf("unsupported tls protocol version %s", tlsConfig.TlsMinimumProtocolVersion) + return nil, fmt.Errorf("unsupported tls protocol version %s", tlsConfig.TlsMinimumProtocolVersion) } tlsParams.TlsMinimumProtocolVersion = tls.TlsParameters_TlsProtocol(tlsProtocolValue) } @@ -140,41 +141,73 @@ func (l *listenerBuilder) AddHTTPManagerFilterChains(httpConnectionManager *hcm. if tlsConfig.TlsMaximumProtocolVersion != "" { tlsProtocolValue, ok := tls.TlsParameters_TlsProtocol_value[tlsConfig.TlsMaximumProtocolVersion] if !ok { - return fmt.Errorf("unsupported tls protocol version %s", tlsConfig.TlsMaximumProtocolVersion) + return nil, fmt.Errorf("unsupported tls protocol version %s", tlsConfig.TlsMaximumProtocolVersion) } - tlsParams.TlsMinimumProtocolVersion = tls.TlsParameters_TlsProtocol(tlsProtocolValue) + tlsParams.TlsMaximumProtocolVersion = tls.TlsParameters_TlsProtocol(tlsProtocolValue) } if err := tlsParams.Validate(); err != nil { - return fmt.Errorf("invalid tls parameters: %w", err) + return nil, fmt.Errorf("invalid tls parameters: %w", err) } - tlsDownstreamContext := &tls.DownstreamTlsContext{ - CommonTlsContext: &tls.CommonTlsContext{ - TlsCertificates: tlsCerts, - TlsParams: tlsParams, - }, + return tlsParams, nil +} + +// AddHTTPManagerFilterChains inserts HTTP Manager as the listener filter chain(s) +// If certificates are present an additional TLS-enabled filter chain is added and protocol type detection is enabled with TLS Inspector Listener filter. +func (l *listenerBuilder) AddHTTPManagerFilterChains(httpConnectionManager *hcm.HttpConnectionManager, tlsConfig TLS) error { + anyHTTPManagerConfig, err := anypb.New(httpConnectionManager) + if err != nil { + return fmt.Errorf("failed to add http manager to the filter chain: cannot convert to Any message type: %w", err) + } + hcmFilter := &listener.Filter{ + Name: wellknown.HTTPConnectionManager, + ConfigType: &listener.Filter_TypedConfig{TypedConfig: anyHTTPManagerConfig}, + } + // Plain HTTP manager filter chain + hcmPlainChain := &listener.FilterChain{ + Filters: []*listener.Filter{hcmFilter}, } + l.addListenerFilterChain(hcmPlainChain) - if err := tlsDownstreamContext.Validate(); err != nil { - return fmt.Errorf("invalid tls downstream context: %w", err) + if len(tlsConfig.Certificates) == 0 { + return nil } - anyTls, err := anypb.New(tlsDownstreamContext) + // When certificates are present, we add an additional Listener filter chain that is selected when the connection protocol type is tls. + // HTTP Manager configuration is the same. + // Enable TLS Inspector in the Listener to detect plain http or tls requests. + l.addListenerFilter(&listener.ListenerFilter{Name: wellknown.TLSInspector}) + + // Make sure plain http manager filter chain is selected when protocol type is raw_buffer (not tls). + hcmPlainChain.FilterChainMatch = &listener.FilterChainMatch{TransportProtocol: "raw_buffer"} + + tlsParams, err := getTLSParameters(tlsConfig) if err != nil { - return fmt.Errorf("unable to marshal TLS config to typed struct: %w", err) + return fmt.Errorf("unable to get TLS Parameters: %w", err) } - hcmSecureChain.TransportSocket = &core.TransportSocket{ - Name: wellknown.TransportSocketTLS, - ConfigType: &core.TransportSocket_TypedConfig{TypedConfig: anyTls}, - } + for _, tlsCert := range tlsConfig.Certificates { + certChain, err := cert.DecodeCertificates([]byte(tlsCert.Cert)) + if err != nil { + return fmt.Errorf("unable to decode certificates: %w", err) + } - if err := hcmPlainChain.Validate(); err != nil { - return fmt.Errorf("invalid secure listener chain: %w", err) - } + if len(certChain) == 0 { + return fmt.Errorf("resulting cert chain length was 0") + } + + leafCert := certChain[0] + if len(leafCert.DNSNames) == 0 { + return fmt.Errorf("found certificate without SAN. All provided certificates must have at least one SAN") + } - l.addListenerFilterChain(hcmSecureChain) + filterChain, err := makeHTTPSFilterChain(tlsCert, leafCert.DNSNames, tlsParams, anyHTTPManagerConfig) + if err != nil { + return fmt.Errorf("unable to make HTTPS filter chain with hosts: %w", err) + } + l.addListenerFilterChain(filterChain) + } return nil } diff --git a/go.sum b/go.sum index 3d5a16f07..3b41f04d0 100644 --- a/go.sum +++ b/go.sum @@ -266,7 +266,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= @@ -319,7 +318,6 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -496,7 +494,6 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= @@ -559,7 +556,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -571,7 +567,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -791,7 +786,6 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff h1:VX/uD7MK0AHXGiScH3fsieUQUcpmRERPDYtqZdJnA+Q= golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -821,7 +815,6 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=