1/*
2 *
3 * Copyright 2021 Google LLC
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19package s2a
20
21import (
22 "context"
23 "crypto/tls"
24 "errors"
25 "sync"
26
27 "github.com/google/s2a-go/fallback"
28 "github.com/google/s2a-go/stream"
29 "google.golang.org/grpc/credentials"
30
31 s2apbv1 "github.com/google/s2a-go/internal/proto/common_go_proto"
32 s2apb "github.com/google/s2a-go/internal/proto/v2/common_go_proto"
33)
34
35// Identity is the interface for S2A identities.
36type Identity interface {
37 // Name returns the name of the identity.
38 Name() string
39}
40
41type spiffeID struct {
42 spiffeID string
43}
44
45func (s *spiffeID) Name() string { return s.spiffeID }
46
47// NewSpiffeID creates a SPIFFE ID from id.
48func NewSpiffeID(id string) Identity {
49 return &spiffeID{spiffeID: id}
50}
51
52type hostname struct {
53 hostname string
54}
55
56func (h *hostname) Name() string { return h.hostname }
57
58// NewHostname creates a hostname from name.
59func NewHostname(name string) Identity {
60 return &hostname{hostname: name}
61}
62
63type uid struct {
64 uid string
65}
66
67func (h *uid) Name() string { return h.uid }
68
69// NewUID creates a UID from name.
70func NewUID(name string) Identity {
71 return &uid{uid: name}
72}
73
74// VerificationModeType specifies the mode that S2A must use to verify the peer
75// certificate chain.
76type VerificationModeType int
77
78// Three types of verification modes.
79const (
80 Unspecified VerificationModeType = iota
81 Spiffe
82 ConnectToGoogle
83 ReservedCustomVerificationMode3
84 ReservedCustomVerificationMode4
85 ReservedCustomVerificationMode5
86)
87
88// ClientOptions contains the client-side options used to establish a secure
89// channel using the S2A handshaker service.
90type ClientOptions struct {
91 // TargetIdentities contains a list of allowed server identities. One of the
92 // target identities should match the peer identity in the handshake
93 // result; otherwise, the handshake fails.
94 TargetIdentities []Identity
95 // LocalIdentity is the local identity of the client application. If none is
96 // provided, then the S2A will choose the default identity, if one exists.
97 LocalIdentity Identity
98 // S2AAddress is the address of the S2A.
99 S2AAddress string
100 // Optional transport credentials.
101 // If set, this will be used for the gRPC connection to the S2A server.
102 TransportCreds credentials.TransportCredentials
103 // EnsureProcessSessionTickets waits for all session tickets to be sent to
104 // S2A before a process completes.
105 //
106 // This functionality is crucial for processes that complete very soon after
107 // using S2A to establish a TLS connection, but it can be ignored for longer
108 // lived processes.
109 //
110 // Usage example:
111 // func main() {
112 // var ensureProcessSessionTickets sync.WaitGroup
113 // clientOpts := &s2a.ClientOptions{
114 // EnsureProcessSessionTickets: &ensureProcessSessionTickets,
115 // // Set other members.
116 // }
117 // creds, _ := s2a.NewClientCreds(clientOpts)
118 // conn, _ := grpc.Dial(serverAddr, grpc.WithTransportCredentials(creds))
119 // defer conn.Close()
120 //
121 // // Make RPC call.
122 //
123 // // The process terminates right after the RPC call ends.
124 // // ensureProcessSessionTickets can be used to ensure resumption
125 // // tickets are fully processed. If the process is long-lived, using
126 // // ensureProcessSessionTickets is not necessary.
127 // ensureProcessSessionTickets.Wait()
128 // }
129 EnsureProcessSessionTickets *sync.WaitGroup
130 // If true, enables the use of legacy S2Av1.
131 EnableLegacyMode bool
132 // VerificationMode specifies the mode that S2A must use to verify the
133 // peer certificate chain.
134 VerificationMode VerificationModeType
135
136 // Optional fallback after dialing with S2A fails.
137 FallbackOpts *FallbackOptions
138
139 // Generates an S2AStream interface for talking to the S2A server.
140 getS2AStream func(ctx context.Context, s2av2Address string) (stream.S2AStream, error)
141
142 // Serialized user specified policy for server authorization.
143 serverAuthorizationPolicy []byte
144}
145
146// FallbackOptions prescribes the fallback logic that should be taken if the application fails to connect with S2A.
147type FallbackOptions struct {
148 // FallbackClientHandshakeFunc is used to specify fallback behavior when calling s2a.NewClientCreds().
149 // It will be called by ClientHandshake function, after handshake with S2A fails.
150 // s2a.NewClientCreds() ignores the other FallbackDialer field.
151 FallbackClientHandshakeFunc fallback.ClientHandshake
152
153 // FallbackDialer is used to specify fallback behavior when calling s2a.NewS2aDialTLSContextFunc().
154 // It passes in a custom fallback dialer and server address to use after dialing with S2A fails.
155 // s2a.NewS2aDialTLSContextFunc() ignores the other FallbackClientHandshakeFunc field.
156 FallbackDialer *FallbackDialer
157}
158
159// FallbackDialer contains a fallback tls.Dialer and a server address to connect to.
160type FallbackDialer struct {
161 // Dialer specifies a fallback tls.Dialer.
162 Dialer *tls.Dialer
163 // ServerAddr is used by Dialer to establish fallback connection.
164 ServerAddr string
165}
166
167// DefaultClientOptions returns the default client options.
168func DefaultClientOptions(s2aAddress string) *ClientOptions {
169 return &ClientOptions{
170 S2AAddress: s2aAddress,
171 VerificationMode: ConnectToGoogle,
172 }
173}
174
175// ServerOptions contains the server-side options used to establish a secure
176// channel using the S2A handshaker service.
177type ServerOptions struct {
178 // LocalIdentities is the list of local identities that may be assumed by
179 // the server. If no local identity is specified, then the S2A chooses a
180 // default local identity, if one exists.
181 LocalIdentities []Identity
182 // S2AAddress is the address of the S2A.
183 S2AAddress string
184 // Optional transport credentials.
185 // If set, this will be used for the gRPC connection to the S2A server.
186 TransportCreds credentials.TransportCredentials
187 // If true, enables the use of legacy S2Av1.
188 EnableLegacyMode bool
189 // VerificationMode specifies the mode that S2A must use to verify the
190 // peer certificate chain.
191 VerificationMode VerificationModeType
192
193 // Generates an S2AStream interface for talking to the S2A server.
194 getS2AStream func(ctx context.Context, s2av2Address string) (stream.S2AStream, error)
195}
196
197// DefaultServerOptions returns the default server options.
198func DefaultServerOptions(s2aAddress string) *ServerOptions {
199 return &ServerOptions{
200 S2AAddress: s2aAddress,
201 VerificationMode: ConnectToGoogle,
202 }
203}
204
205func toProtoIdentity(identity Identity) (*s2apbv1.Identity, error) {
206 if identity == nil {
207 return nil, nil
208 }
209 switch id := identity.(type) {
210 case *spiffeID:
211 return &s2apbv1.Identity{IdentityOneof: &s2apbv1.Identity_SpiffeId{SpiffeId: id.Name()}}, nil
212 case *hostname:
213 return &s2apbv1.Identity{IdentityOneof: &s2apbv1.Identity_Hostname{Hostname: id.Name()}}, nil
214 case *uid:
215 return &s2apbv1.Identity{IdentityOneof: &s2apbv1.Identity_Uid{Uid: id.Name()}}, nil
216 default:
217 return nil, errors.New("unrecognized identity type")
218 }
219}
220
221func toV2ProtoIdentity(identity Identity) (*s2apb.Identity, error) {
222 if identity == nil {
223 return nil, nil
224 }
225 switch id := identity.(type) {
226 case *spiffeID:
227 return &s2apb.Identity{IdentityOneof: &s2apb.Identity_SpiffeId{SpiffeId: id.Name()}}, nil
228 case *hostname:
229 return &s2apb.Identity{IdentityOneof: &s2apb.Identity_Hostname{Hostname: id.Name()}}, nil
230 case *uid:
231 return &s2apb.Identity{IdentityOneof: &s2apb.Identity_Uid{Uid: id.Name()}}, nil
232 default:
233 return nil, errors.New("unrecognized identity type")
234 }
235}