forked from TrueCloudLab/certificates
Complete cloudcas using CAS v1beta1.
This commit is contained in:
parent
1b1f73dec6
commit
c8d9cb0a1d
8 changed files with 318 additions and 56 deletions
57
cas/apiv1/extension.go
Normal file
57
cas/apiv1/extension.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package apiv1
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// CertificateAuthorityExtension is type used to encode the certificate
|
||||
// authority extension.
|
||||
type CertificateAuthorityExtension struct {
|
||||
Type string
|
||||
CertificateID string `asn1:"optional,omitempty"`
|
||||
KeyValuePairs []string `asn1:"optional,omitempty"`
|
||||
}
|
||||
|
||||
// CreateCertificateAuthorityExtension returns a X.509 extension that shows the
|
||||
// CAS type, id and a list of optional key value pairs.
|
||||
func CreateCertificateAuthorityExtension(typ Type, certificateID string, keyValuePairs ...string) (pkix.Extension, error) {
|
||||
b, err := asn1.Marshal(CertificateAuthorityExtension{
|
||||
Type: typ.String(),
|
||||
CertificateID: certificateID,
|
||||
KeyValuePairs: keyValuePairs,
|
||||
})
|
||||
if err != nil {
|
||||
return pkix.Extension{}, errors.Wrapf(err, "error marshaling certificate id extension")
|
||||
}
|
||||
return pkix.Extension{
|
||||
Id: oidStepCertificateAuthority,
|
||||
Critical: false,
|
||||
Value: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FindCertificateAuthorityExtension returns the certificate authority extension
|
||||
// from a signed certificate.
|
||||
func FindCertificateAuthorityExtension(cert *x509.Certificate) (pkix.Extension, bool) {
|
||||
for _, ext := range cert.Extensions {
|
||||
if ext.Id.Equal(oidStepCertificateAuthority) {
|
||||
return ext, true
|
||||
}
|
||||
}
|
||||
return pkix.Extension{}, false
|
||||
}
|
||||
|
||||
// RemoveCertificateAuthorityExtension removes the certificate authority
|
||||
// extension from a certificate template.
|
||||
func RemoveCertificateAuthorityExtension(cert *x509.Certificate) {
|
||||
for i, ext := range cert.ExtraExtensions {
|
||||
if ext.Id.Equal(oidStepCertificateAuthority) {
|
||||
cert.ExtraExtensions = append(cert.ExtraExtensions[:i], cert.ExtraExtensions[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,3 +30,11 @@ func (o *Options) Validate() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasType returns if the options have the given type.
|
||||
func (o *Options) HasType(t Type) bool {
|
||||
if o == nil {
|
||||
return SoftCAS == t.String()
|
||||
}
|
||||
return Type(o.Type).String() == t.String()
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@ import (
|
|||
)
|
||||
|
||||
type CreateCertificateRequest struct {
|
||||
Template *x509.Certificate
|
||||
Issuer *x509.Certificate
|
||||
Signer crypto.Signer
|
||||
Lifetime time.Duration
|
||||
|
||||
Template *x509.Certificate
|
||||
Issuer *x509.Certificate
|
||||
Signer crypto.Signer
|
||||
Lifetime time.Duration
|
||||
Backdate time.Duration
|
||||
RequestID string
|
||||
}
|
||||
type CreateCertificateResponse struct {
|
||||
|
@ -19,8 +19,24 @@ type CreateCertificateResponse struct {
|
|||
CertificateChain []*x509.Certificate
|
||||
}
|
||||
|
||||
type RenewCertificateRequest struct{}
|
||||
type RenewCertificateResponse struct{}
|
||||
type RenewCertificateRequest struct {
|
||||
Template *x509.Certificate
|
||||
Issuer *x509.Certificate
|
||||
Signer crypto.Signer
|
||||
Lifetime time.Duration
|
||||
Backdate time.Duration
|
||||
RequestID string
|
||||
}
|
||||
type RenewCertificateResponse struct {
|
||||
Certificate *x509.Certificate
|
||||
CertificateChain []*x509.Certificate
|
||||
}
|
||||
|
||||
type RevokeCertificateRequest struct{}
|
||||
type RevokeCertificateResponse struct{}
|
||||
// RevokeCertificateRequest is the request used to revoke a certificate.
|
||||
type RevokeCertificateRequest struct {
|
||||
Certificate *x509.Certificate
|
||||
}
|
||||
type RevokeCertificateResponse struct {
|
||||
Certificate *x509.Certificate
|
||||
CertificateChain []*x509.Certificate
|
||||
}
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
package apiv1
|
||||
|
||||
import (
|
||||
"encoding/asn1"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
oidStepRoot = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64}
|
||||
oidStepCertificateAuthority = append(asn1.ObjectIdentifier(nil), append(oidStepRoot, 2)...)
|
||||
)
|
||||
|
||||
// CertificateAuthorityService is the interface implemented to support external
|
||||
// certificate authorities.
|
||||
type CertificateAuthorityService interface {
|
||||
|
@ -11,12 +21,24 @@ type CertificateAuthorityService interface {
|
|||
// Type represents the KMS type used.
|
||||
type Type string
|
||||
|
||||
//
|
||||
const (
|
||||
// DefaultCAS is a CertificateAuthorityService using software.
|
||||
DefaultCAS = ""
|
||||
// SoftCAS is a CertificateAuthorityService using software.
|
||||
SoftCAS = "softcas"
|
||||
SoftCAS = "SoftCAS"
|
||||
// CloudCAS is a CertificateAuthorityService using Google Cloud CAS.
|
||||
CloudCAS = "cloudcas"
|
||||
CloudCAS = "CloudCAS"
|
||||
)
|
||||
|
||||
// String returns the given type as a string. All the letters will be lowercase.
|
||||
func (t Type) String() string {
|
||||
if t == "" {
|
||||
return SoftCAS
|
||||
}
|
||||
for _, s := range []string{SoftCAS, CloudCAS} {
|
||||
if strings.EqualFold(s, string(t)) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return string(t)
|
||||
}
|
||||
|
|
|
@ -63,7 +63,6 @@ func createCertificateConfig(tpl *x509.Certificate) (*pb.Certificate_Config, err
|
|||
}
|
||||
|
||||
func createPublicKey(key crypto.PublicKey) (*pb.PublicKey, error) {
|
||||
pk := new(pb.PublicKey)
|
||||
switch key := key.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
asn1Bytes, err := x509.MarshalPKIXPublicKey(key)
|
||||
|
@ -88,8 +87,6 @@ func createPublicKey(key crypto.PublicKey) (*pb.PublicKey, error) {
|
|||
default:
|
||||
return nil, errors.Errorf("unsupported public key type: %T", key)
|
||||
}
|
||||
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
func createSubject(cert *x509.Certificate) *pb.Subject {
|
||||
|
@ -138,12 +135,43 @@ func createSubjectAlternativeNames(cert *x509.Certificate) *pb.SubjectAltNames {
|
|||
|
||||
// Add extra SANs coming from the extensions
|
||||
if ext, ok := findExtraExtension(cert, oidExtensionSubjectAltName); ok {
|
||||
ret.CustomSans = []*pb.X509Extension{{
|
||||
ObjectId: createObjectID(ext.Id),
|
||||
Critical: ext.Critical,
|
||||
Value: ext.Value,
|
||||
}}
|
||||
var rawValues []asn1.RawValue
|
||||
if _, err := asn1.Unmarshal(ext.Value, &rawValues); err == nil {
|
||||
var newValues []asn1.RawValue
|
||||
for _, v := range rawValues {
|
||||
switch v.Tag {
|
||||
case nameTypeDNS:
|
||||
if len(ret.DnsNames) == 0 {
|
||||
newValues = append(newValues, v)
|
||||
}
|
||||
case nameTypeEmail:
|
||||
if len(ret.EmailAddresses) == 0 {
|
||||
newValues = append(newValues, v)
|
||||
}
|
||||
case nameTypeIP:
|
||||
if len(ret.IpAddresses) == 0 {
|
||||
newValues = append(newValues, v)
|
||||
}
|
||||
case nameTypeURI:
|
||||
if len(ret.Uris) == 0 {
|
||||
newValues = append(newValues, v)
|
||||
}
|
||||
default:
|
||||
newValues = append(newValues, v)
|
||||
}
|
||||
}
|
||||
if len(newValues) > 0 {
|
||||
if b, err := asn1.Marshal(newValues); err == nil {
|
||||
ret.CustomSans = []*pb.X509Extension{{
|
||||
ObjectId: createObjectID(ext.Id),
|
||||
Critical: ext.Critical,
|
||||
Value: b,
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
|
@ -210,12 +238,14 @@ func createReusableConfig(cert *x509.Certificate) *pb.ReusableConfigWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
extraExtensions := make([]*pb.X509Extension, len(cert.ExtraExtensions))
|
||||
for i, ext := range cert.ExtraExtensions {
|
||||
extraExtensions[i] = &pb.X509Extension{
|
||||
ObjectId: createObjectID(ext.Id),
|
||||
Critical: ext.Critical,
|
||||
Value: ext.Value,
|
||||
var extraExtensions []*pb.X509Extension
|
||||
for _, ext := range cert.ExtraExtensions {
|
||||
if !ext.Id.Equal(oidExtensionSubjectAltName) {
|
||||
extraExtensions = append(extraExtensions, &pb.X509Extension{
|
||||
ObjectId: createObjectID(ext.Id),
|
||||
Critical: ext.Critical,
|
||||
Value: ext.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,14 +3,18 @@ package cloudcas
|
|||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
privateca "cloud.google.com/go/security/privateca/apiv1beta1"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/cas/apiv1"
|
||||
privatecapb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1"
|
||||
"google.golang.org/api/option"
|
||||
pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1"
|
||||
durationpb "google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
|
@ -20,6 +24,16 @@ func init() {
|
|||
})
|
||||
}
|
||||
|
||||
func debug(v interface{}) {
|
||||
b, _ := json.MarshalIndent(v, "", " ")
|
||||
fmt.Println(string(b))
|
||||
}
|
||||
|
||||
var (
|
||||
stepOIDRoot = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64}
|
||||
stepOIDCertificateAuthority = append(asn1.ObjectIdentifier(nil), append(stepOIDRoot, 2)...)
|
||||
)
|
||||
|
||||
// CloudCAS implements a Certificate Authority Service using Google Cloud CAS.
|
||||
type CloudCAS struct {
|
||||
client *privateca.CertificateAuthorityClient
|
||||
|
@ -31,14 +45,19 @@ type caClient interface{}
|
|||
// New creates a new CertificateAuthorityService implementation using Google
|
||||
// Cloud CAS.
|
||||
func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
|
||||
client, err := privateca.NewCertificateAuthorityClient(ctx)
|
||||
var cloudOpts []option.ClientOption
|
||||
if opts.CredentialsFile != "" {
|
||||
cloudOpts = append(cloudOpts, option.WithCredentialsFile(opts.CredentialsFile))
|
||||
}
|
||||
|
||||
client, err := privateca.NewCertificateAuthorityClient(ctx, cloudOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating client")
|
||||
}
|
||||
|
||||
return &CloudCAS{
|
||||
client: client,
|
||||
certificateAuthority: "",
|
||||
certificateAuthority: "projects/smallstep-cas-test/locations/us-west1/certificateAuthorities/Smallstep-Test-Intermediate-CA",
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -51,52 +70,131 @@ func (c *CloudCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv
|
|||
return nil, errors.New("createCertificateRequest `lifetime` cannot be 0")
|
||||
}
|
||||
|
||||
certConfig, err := createCertificateConfig(req.Template)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := defaultContext()
|
||||
defer cancel()
|
||||
|
||||
certpb, err := c.client.CreateCertificate(ctx, &privatecapb.CreateCertificateRequest{
|
||||
Parent: c.certificateAuthority,
|
||||
CertificateId: "",
|
||||
Certificate: &privatecapb.Certificate{
|
||||
CertificateConfig: certConfig,
|
||||
Lifetime: durationpb.New(req.Lifetime),
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
RequestId: req.RequestID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cloudCAS CreateCertificate failed")
|
||||
}
|
||||
|
||||
cert, err := parseCertificate(certpb.PemCertificate)
|
||||
cert, chain, err := c.createCertificate(req.Template, req.Lifetime, req.RequestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &apiv1.CreateCertificateResponse{
|
||||
Certificate: cert,
|
||||
Certificate: cert,
|
||||
CertificateChain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RenewCertificate renews the given certificate using Google Cloud CAS.
|
||||
// Google's CAS does not support the renew operation, so this method uses
|
||||
// CreateCertificate.
|
||||
func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
switch {
|
||||
case req.Template == nil:
|
||||
return nil, errors.New("renewCertificate `template` cannot be nil")
|
||||
case req.Lifetime == 0:
|
||||
return nil, errors.New("renewCertificate `lifetime` cannot be 0")
|
||||
}
|
||||
|
||||
cert, chain, err := c.createCertificate(req.Template, req.Lifetime, req.RequestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &apiv1.RenewCertificateResponse{
|
||||
Certificate: cert,
|
||||
CertificateChain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RevokeCertificate a certificate using Google Cloud CAS.
|
||||
func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
|
||||
if req.Certificate == nil {
|
||||
return nil, errors.New("revokeCertificate `certificate` cannot be nil")
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
ext, ok := apiv1.FindCertificateAuthorityExtension(req.Certificate)
|
||||
if !ok {
|
||||
return nil, errors.New("error revoking certificate: certificate authority extension was not found")
|
||||
}
|
||||
|
||||
var cae apiv1.CertificateAuthorityExtension
|
||||
if _, err := asn1.Unmarshal(ext.Value, &ext); err != nil {
|
||||
return nil, errors.Wrap(err, "error unmarshaling certificate authority extension")
|
||||
}
|
||||
|
||||
ctx, cancel := defaultContext()
|
||||
defer cancel()
|
||||
|
||||
certpb, err := c.client.RevokeCertificate(ctx, &pb.RevokeCertificateRequest{
|
||||
Name: c.certificateAuthority + "/certificates/" + cae.CertificateID,
|
||||
Reason: pb.RevocationReason_REVOCATION_REASON_UNSPECIFIED,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cloudCAS RevokeCertificate failed")
|
||||
}
|
||||
|
||||
cert, chain, err := getCertificateAndChain(certpb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &apiv1.RevokeCertificateResponse{
|
||||
Certificate: cert,
|
||||
CertificateChain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CloudCAS) createCertificate(tpl *x509.Certificate, lifetime time.Duration, requestID string) (*x509.Certificate, []*x509.Certificate, error) {
|
||||
// Removes the CAS extension if it exists.
|
||||
apiv1.RemoveCertificateAuthorityExtension(tpl)
|
||||
|
||||
// Create new CAS extension with the certificate id.
|
||||
id, err := createCertificateID()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
casExtension, err := apiv1.CreateCertificateAuthorityExtension(apiv1.CloudCAS, id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tpl.ExtraExtensions = append(tpl.ExtraExtensions, casExtension)
|
||||
|
||||
// Create and submit certificate
|
||||
certConfig, err := createCertificateConfig(tpl)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := defaultContext()
|
||||
defer cancel()
|
||||
|
||||
cert, err := c.client.CreateCertificate(ctx, &pb.CreateCertificateRequest{
|
||||
Parent: c.certificateAuthority,
|
||||
CertificateId: id,
|
||||
Certificate: &pb.Certificate{
|
||||
CertificateConfig: certConfig,
|
||||
Lifetime: durationpb.New(lifetime),
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
RequestId: requestID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cloudCAS CreateCertificate failed")
|
||||
}
|
||||
|
||||
// Return certificate and certificate chain
|
||||
return getCertificateAndChain(cert)
|
||||
}
|
||||
|
||||
func defaultContext() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), 15*time.Second)
|
||||
}
|
||||
|
||||
func createCertificateID() (string, error) {
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error creating certificate id")
|
||||
}
|
||||
return id.String(), nil
|
||||
}
|
||||
|
||||
func parseCertificate(pemCert string) (*x509.Certificate, error) {
|
||||
block, _ := pem.Decode([]byte(pemCert))
|
||||
if block == nil {
|
||||
|
@ -108,3 +206,22 @@ func parseCertificate(pemCert string) (*x509.Certificate, error) {
|
|||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func getCertificateAndChain(certpb *pb.Certificate) (*x509.Certificate, []*x509.Certificate, error) {
|
||||
cert, err := parseCertificate(certpb.PemCertificate)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pemChain := certpb.PemCertificateChain[:len(certpb.PemCertificateChain)-1]
|
||||
chain := make([]*x509.Certificate, len(pemChain))
|
||||
for i := range pemChain {
|
||||
chain[i], err = parseCertificate(pemChain[i])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cert, chain, nil
|
||||
|
||||
}
|
||||
|
|
9
go.mod
9
go.mod
|
@ -4,10 +4,12 @@ go 1.14
|
|||
|
||||
require (
|
||||
cloud.google.com/go v0.65.1-0.20200904011802-3c2db50b5678
|
||||
|
||||
github.com/Masterminds/sprig/v3 v3.1.0
|
||||
github.com/aws/aws-sdk-go v1.30.29
|
||||
github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/go-piv/piv-go v1.5.0
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/googleapis/gax-go/v2 v2.0.5
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect
|
||||
github.com/lunixbochs/vtclean v1.0.0 // indirect
|
||||
|
@ -24,11 +26,16 @@ require (
|
|||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||
google.golang.org/api v0.31.0
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d
|
||||
google.golang.org/grpc v1.31.1
|
||||
google.golang.org/grpc v1.32.0
|
||||
google.golang.org/protobuf v1.25.0
|
||||
gopkg.in/square/go-jose.v2 v2.5.1
|
||||
// cloud.google.com/go/security/privateca/apiv1alpha1 v0.0.0
|
||||
// google.golang.org/genproto/googleapis/cloud/security/privateca/v1alpha1 v0.0.0
|
||||
)
|
||||
|
||||
// replace github.com/smallstep/cli => ../cli
|
||||
// replace github.com/smallstep/nosql => ../nosql
|
||||
// replace go.step.sm/crypto => ../crypto
|
||||
|
||||
// replace cloud.google.com/go/security/privateca/apiv1alpha1 => ./pkg/cloud.google.com/go/security/privateca/apiv1alpha1
|
||||
// replace google.golang.org/genproto/googleapis/cloud/security/privateca/v1alpha1 => ./pkg/google.golang.org/genproto/googleapis/cloud/security/privateca/v1alpha1
|
||||
|
|
5
go.sum
5
go.sum
|
@ -298,6 +298,8 @@ github.com/google/trillian-examples v0.0.0-20190603134952-4e75ba15216c/go.mod h1
|
|||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/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=
|
||||
|
@ -911,6 +913,7 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D
|
|||
google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200910191746-8ad3c7ee2cd1 h1:Oi/dETbxPPblvoi4hgkzJun62A4dwuBsTM0UcZYpN3U=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
@ -929,6 +932,8 @@ google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
|
|||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
|
Loading…
Reference in a new issue