1
// Copyright Istio Authors
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
7
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
22
"google.golang.org/grpc"
23
"google.golang.org/grpc/metadata"
24
"google.golang.org/protobuf/types/known/structpb"
26
pb "istio.io/api/security/v1alpha1"
27
istiogrpc "istio.io/istio/pilot/pkg/grpc"
28
"istio.io/istio/pkg/log"
29
"istio.io/istio/pkg/security"
30
"istio.io/istio/security/pkg/nodeagent/caclient"
34
bearerTokenPrefix = "Bearer "
37
var citadelClientLog = log.RegisterScope("citadelclient", "citadel client debugging")
39
type CitadelClient struct {
40
// It means enable tls connection to Citadel if this is not nil.
42
client pb.IstioCertificateServiceClient
44
provider *caclient.TokenProvider
45
opts *security.Options
48
type TLSOptions struct {
54
// NewCitadelClient create a CA client for Citadel.
55
func NewCitadelClient(opts *security.Options, tlsOpts *TLSOptions) (*CitadelClient, error) {
59
provider: caclient.NewCATokenProvider(opts),
62
conn, err := c.buildConnection()
64
citadelClientLog.Errorf("Failed to connect to endpoint %s: %v", opts.CAEndpoint, err)
65
return nil, fmt.Errorf("failed to connect to endpoint %s", opts.CAEndpoint)
68
c.client = pb.NewIstioCertificateServiceClient(conn)
72
func (c *CitadelClient) Close() {
78
// CSRSign calls Citadel to sign a CSR.
79
func (c *CitadelClient) CSRSign(csrPEM []byte, certValidTTLInSec int64) (res []string, err error) {
80
crMetaStruct := &structpb.Struct{
81
Fields: map[string]*structpb.Value{
82
security.CertSigner: {
83
Kind: &structpb.Value_StringValue{StringValue: c.opts.CertSigner},
87
req := &pb.IstioCertificateRequest{
89
ValidityDuration: certValidTTLInSec,
90
Metadata: crMetaStruct,
92
// TODO(hzxuzhonghu): notify caclient rebuilding only when root cert is updated.
93
// It can happen when the istiod dns certs is resigned after root cert is updated,
94
// in this case, the ca grpc client can not automatically connect to istiod after the underlying network connection closed.
95
// Becase that the grpc client still use the old tls configuration to reconnect to istiod.
96
// So here we need to rebuild the caClient in order to use the new root cert.
99
citadelClientLog.Errorf("failed to sign CSR: %v", err)
100
if err := c.reconnect(); err != nil {
101
citadelClientLog.Errorf("failed reconnect: %v", err)
106
ctx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs("ClusterID", c.opts.ClusterID))
107
resp, err := c.client.CreateCertificate(ctx, req)
109
return nil, fmt.Errorf("create certificate: %v", err)
112
if len(resp.CertChain) <= 1 {
113
return nil, errors.New("invalid empty CertChain")
116
return resp.CertChain, nil
119
func (c *CitadelClient) getTLSOptions() *istiogrpc.TLSOptions {
120
if c.tlsOpts != nil {
121
return &istiogrpc.TLSOptions{
122
RootCert: c.tlsOpts.RootCert,
124
Cert: c.tlsOpts.Cert,
125
ServerAddress: c.opts.CAEndpoint,
126
SAN: c.opts.CAEndpointSAN,
132
func (c *CitadelClient) buildConnection() (*grpc.ClientConn, error) {
133
tlsOpts := c.getTLSOptions()
134
opts, err := istiogrpc.ClientOptions(nil, tlsOpts)
139
grpc.WithPerRPCCredentials(c.provider),
140
security.CARetryInterceptor(),
142
conn, err := grpc.Dial(c.opts.CAEndpoint, opts...)
144
citadelClientLog.Errorf("Failed to connect to endpoint %s: %v", c.opts.CAEndpoint, err)
145
return nil, fmt.Errorf("failed to connect to endpoint %s", c.opts.CAEndpoint)
151
func (c *CitadelClient) reconnect() error {
152
if err := c.conn.Close(); err != nil {
153
return fmt.Errorf("failed to close connection: %v", err)
156
conn, err := c.buildConnection()
161
c.client = pb.NewIstioCertificateServiceClient(conn)
162
citadelClientLog.Info("recreated connection")
166
// GetRootCertBundle: Citadel (Istiod) CA doesn't publish any endpoint to retrieve CA certs
167
func (c *CitadelClient) GetRootCertBundle() ([]string, error) {
168
return []string{}, nil