| // Copyright Istio Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package caclient |
| |
| import ( |
| "context" |
| "crypto/x509" |
| "errors" |
| "fmt" |
| "regexp" |
| "strings" |
| ) |
| |
| import ( |
| "github.com/google/uuid" |
| "google.golang.org/grpc" |
| "google.golang.org/grpc/credentials" |
| "google.golang.org/grpc/credentials/insecure" |
| "google.golang.org/grpc/metadata" |
| "google.golang.org/protobuf/types/known/durationpb" |
| "istio.io/pkg/env" |
| "istio.io/pkg/log" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/pkg/bootstrap/platform" |
| "github.com/apache/dubbo-go-pixiu/pkg/security" |
| "github.com/apache/dubbo-go-pixiu/security/pkg/nodeagent/caclient" |
| gcapb "github.com/apache/dubbo-go-pixiu/security/proto/providers/google" |
| ) |
| |
| const hubIDPPrefix = "https://gkehub.googleapis.com/" |
| |
| var ( |
| googleCAClientLog = log.RegisterScope("googleca", "Google CA client debugging", 0) |
| envGkeClusterURL = env.RegisterStringVar("GKE_CLUSTER_URL", "", "The url of GKE cluster").Get() |
| ) |
| |
| type googleCAClient struct { |
| caEndpoint string |
| enableTLS bool |
| client gcapb.MeshCertificateServiceClient |
| conn *grpc.ClientConn |
| } |
| |
| // NewGoogleCAClient create a CA client for Google CA. |
| func NewGoogleCAClient(endpoint string, tls bool, provider *caclient.TokenProvider) (security.Client, error) { |
| c := &googleCAClient{ |
| caEndpoint: endpoint, |
| enableTLS: tls, |
| } |
| |
| var opts grpc.DialOption |
| var err error |
| if tls { |
| opts, err = c.getTLSDialOption() |
| if err != nil { |
| return nil, err |
| } |
| } else { |
| opts = grpc.WithTransportCredentials(insecure.NewCredentials()) |
| } |
| |
| conn, err := grpc.Dial(endpoint, |
| opts, |
| grpc.WithPerRPCCredentials(provider), |
| security.CARetryInterceptor(), |
| ) |
| if err != nil { |
| googleCAClientLog.Errorf("Failed to connect to endpoint %s: %v", endpoint, err) |
| return nil, fmt.Errorf("failed to connect to endpoint %s", endpoint) |
| } |
| |
| c.conn = conn |
| c.client = gcapb.NewMeshCertificateServiceClient(conn) |
| return c, nil |
| } |
| |
| // CSR Sign calls Google CA to sign a CSR. |
| func (cl *googleCAClient) CSRSign(csrPEM []byte, certValidTTLInSec int64) ([]string, error) { |
| req := &gcapb.MeshCertificateRequest{ |
| RequestId: uuid.New().String(), |
| Csr: string(csrPEM), |
| Validity: &durationpb.Duration{Seconds: certValidTTLInSec}, |
| } |
| |
| out := metadata.New(nil) |
| gkeClusterURL := envGkeClusterURL |
| if envGkeClusterURL == "" && platform.IsGCP() { |
| gkeClusterURL = platform.NewGCP().Metadata()[platform.GCPClusterURL] |
| } |
| zone := parseZone(gkeClusterURL) |
| if zone != "" { |
| out["x-goog-request-params"] = []string{fmt.Sprintf("location=locations/%s", zone)} |
| } |
| |
| ctx := metadata.NewOutgoingContext(context.Background(), out) |
| resp, err := cl.client.CreateCertificate(ctx, req) |
| if err != nil { |
| googleCAClientLog.Errorf("Failed to create certificate: %v", err) |
| googleCAClientLog.Debugf("Original request %v, resp %v", req, resp) |
| return nil, err |
| } |
| |
| if len(resp.CertChain) <= 1 { |
| googleCAClientLog.Errorf("CertChain length is %d, expected more than 1", len(resp.CertChain)) |
| return nil, errors.New("invalid response cert chain") |
| } |
| googleCAClientLog.Infof("Cert created with GoogleCA %s chain length %d", zone, len(resp.CertChain)) |
| |
| return resp.CertChain, nil |
| } |
| |
| func (cl *googleCAClient) Close() { |
| if cl.conn != nil { |
| cl.conn.Close() |
| } |
| } |
| |
| func (cl *googleCAClient) getTLSDialOption() (grpc.DialOption, error) { |
| // Load the system default root certificates. |
| pool, err := x509.SystemCertPool() |
| if err != nil { |
| googleCAClientLog.Errorf("could not get SystemCertPool: %v", err) |
| return nil, errors.New("could not get SystemCertPool") |
| } |
| creds := credentials.NewClientTLSFromCert(pool, "") |
| return grpc.WithTransportCredentials(creds), nil |
| } |
| |
| // GetRootCertBundle: Google Mesh CA doesn't publish any endpoint to retrieve CA certs |
| func (cl *googleCAClient) GetRootCertBundle() ([]string, error) { |
| return []string{}, nil |
| } |
| |
| func parseZone(clusterURL string) string { |
| // for Hub IDNS, the input is https://gkehub.googleapis.com/projects/HUB_PROJECT_ID/locations/global/memberships/MEMBERSHIP_ID which is global |
| if strings.HasPrefix(clusterURL, hubIDPPrefix) { |
| return "" |
| } |
| // input: https://container.googleapis.com/v1/projects/testproj/locations/us-central1-c/clusters/cluster1 |
| // output: us-central1-c |
| rgx := regexp.MustCompile(`.*/projects/(.*)/locations/(.*)/clusters/.*`) |
| rs := rgx.FindStringSubmatch(clusterURL) |
| if len(rs) < 3 { |
| return "" |
| } |
| return rs[2] |
| } |