| /* |
| * |
| * Copyright 2021 Google LLC |
| * |
| * 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 |
| * |
| * https://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 s2a |
| |
| import ( |
| "crypto/tls" |
| "errors" |
| "sync" |
| |
| "github.com/google/s2a-go/fallback" |
| "github.com/google/s2a-go/stream" |
| "google.golang.org/grpc/credentials" |
| |
| s2av1pb "github.com/google/s2a-go/internal/proto/common_go_proto" |
| s2apb "github.com/google/s2a-go/internal/proto/v2/common_go_proto" |
| ) |
| |
| // Identity is the interface for S2A identities. |
| type Identity interface { |
| // Name returns the name of the identity. |
| Name() string |
| Attributes() map[string]string |
| } |
| |
| type UnspecifiedID struct { |
| Attr map[string]string |
| } |
| |
| func (u *UnspecifiedID) Name() string { return "" } |
| |
| func (u *UnspecifiedID) Attributes() map[string]string { |
| return u.Attr |
| } |
| |
| type spiffeID struct { |
| spiffeID string |
| } |
| |
| func (s *spiffeID) Name() string { return s.spiffeID } |
| |
| func (spiffeID) Attributes() map[string]string { return nil } |
| |
| // NewSpiffeID creates a SPIFFE ID from id. |
| func NewSpiffeID(id string) Identity { return &spiffeID{spiffeID: id} } |
| |
| type hostname struct { |
| hostname string |
| } |
| |
| func (h *hostname) Name() string { return h.hostname } |
| |
| func (hostname) Attributes() map[string]string { return nil } |
| |
| // NewHostname creates a hostname from name. |
| func NewHostname(name string) Identity { return &hostname{hostname: name} } |
| |
| type uid struct { |
| uid string |
| } |
| |
| func (h *uid) Name() string { return h.uid } |
| |
| func (uid) Attributes() map[string]string { return nil } |
| |
| // NewUID creates a UID from name. |
| func NewUID(name string) Identity { return &uid{uid: name} } |
| |
| // VerificationModeType specifies the mode that S2A must use to verify the peer |
| // certificate chain. |
| type VerificationModeType int |
| |
| // Three types of verification modes. |
| const ( |
| Unspecified VerificationModeType = iota |
| Spiffe |
| ConnectToGoogle |
| ReservedCustomVerificationMode3 |
| ReservedCustomVerificationMode4 |
| ReservedCustomVerificationMode5 |
| ReservedCustomVerificationMode6 |
| ) |
| |
| // ClientOptions contains the client-side options used to establish a secure |
| // channel using the S2A handshaker service. |
| type ClientOptions struct { |
| // TargetIdentities contains a list of allowed server identities. One of the |
| // target identities should match the peer identity in the handshake |
| // result; otherwise, the handshake fails. |
| TargetIdentities []Identity |
| // LocalIdentity is the local identity of the client application. If none is |
| // provided, then the S2A will choose the default identity, if one exists. |
| LocalIdentity Identity |
| // S2AAddress is the address of the S2A. |
| S2AAddress string |
| // Optional transport credentials. |
| // If set, this will be used for the gRPC connection to the S2A server. |
| TransportCreds credentials.TransportCredentials |
| // EnsureProcessSessionTickets waits for all session tickets to be sent to |
| // S2A before a process completes. |
| // |
| // This functionality is crucial for processes that complete very soon after |
| // using S2A to establish a TLS connection, but it can be ignored for longer |
| // lived processes. |
| // |
| // Usage example: |
| // func main() { |
| // var ensureProcessSessionTickets sync.WaitGroup |
| // clientOpts := &s2a.ClientOptions{ |
| // EnsureProcessSessionTickets: &ensureProcessSessionTickets, |
| // // Set other members. |
| // } |
| // creds, _ := s2a.NewClientCreds(clientOpts) |
| // conn, _ := grpc.Dial(serverAddr, grpc.WithTransportCredentials(creds)) |
| // defer conn.Close() |
| // |
| // // Make RPC call. |
| // |
| // // The process terminates right after the RPC call ends. |
| // // ensureProcessSessionTickets can be used to ensure resumption |
| // // tickets are fully processed. If the process is long-lived, using |
| // // ensureProcessSessionTickets is not necessary. |
| // ensureProcessSessionTickets.Wait() |
| // } |
| EnsureProcessSessionTickets *sync.WaitGroup |
| // If true, enables the use of legacy S2Av1. |
| EnableLegacyMode bool |
| // VerificationMode specifies the mode that S2A must use to verify the |
| // peer certificate chain. |
| VerificationMode VerificationModeType |
| |
| // Optional fallback after dialing with S2A fails. |
| FallbackOpts *FallbackOptions |
| |
| // Generates an S2AStream interface for talking to the S2A server. |
| getS2AStream stream.GetS2AStream |
| |
| // Serialized user specified policy for server authorization. |
| serverAuthorizationPolicy []byte |
| } |
| |
| // FallbackOptions prescribes the fallback logic that should be taken if the application fails to connect with S2A. |
| type FallbackOptions struct { |
| // FallbackClientHandshakeFunc is used to specify fallback behavior when calling s2a.NewClientCreds(). |
| // It will be called by ClientHandshake function, after handshake with S2A fails. |
| // s2a.NewClientCreds() ignores the other FallbackDialer field. |
| FallbackClientHandshakeFunc fallback.ClientHandshake |
| |
| // FallbackDialer is used to specify fallback behavior when calling s2a.NewS2aDialTLSContextFunc(). |
| // It passes in a custom fallback dialer and server address to use after dialing with S2A fails. |
| // s2a.NewS2aDialTLSContextFunc() ignores the other FallbackClientHandshakeFunc field. |
| FallbackDialer *FallbackDialer |
| } |
| |
| // FallbackDialer contains a fallback tls.Dialer and a server address to connect to. |
| type FallbackDialer struct { |
| // Dialer specifies a fallback tls.Dialer. |
| Dialer *tls.Dialer |
| // ServerAddr is used by Dialer to establish fallback connection. |
| ServerAddr string |
| } |
| |
| // DefaultClientOptions returns the default client options. |
| func DefaultClientOptions(s2aAddress string) *ClientOptions { |
| return &ClientOptions{ |
| S2AAddress: s2aAddress, |
| VerificationMode: ConnectToGoogle, |
| } |
| } |
| |
| // ServerOptions contains the server-side options used to establish a secure |
| // channel using the S2A handshaker service. |
| type ServerOptions struct { |
| // LocalIdentities is the list of local identities that may be assumed by |
| // the server. If no local identity is specified, then the S2A chooses a |
| // default local identity, if one exists. |
| LocalIdentities []Identity |
| // S2AAddress is the address of the S2A. |
| S2AAddress string |
| // Optional transport credentials. |
| // If set, this will be used for the gRPC connection to the S2A server. |
| TransportCreds credentials.TransportCredentials |
| // If true, enables the use of legacy S2Av1. |
| EnableLegacyMode bool |
| // VerificationMode specifies the mode that S2A must use to verify the |
| // peer certificate chain. |
| VerificationMode VerificationModeType |
| |
| // Generates an S2AStream interface for talking to the S2A server. |
| getS2AStream stream.GetS2AStream |
| } |
| |
| // DefaultServerOptions returns the default server options. |
| func DefaultServerOptions(s2aAddress string) *ServerOptions { |
| return &ServerOptions{ |
| S2AAddress: s2aAddress, |
| VerificationMode: ConnectToGoogle, |
| } |
| } |
| |
| func toProtoIdentity(identity Identity) (*s2av1pb.Identity, error) { |
| if identity == nil { |
| return nil, nil |
| } |
| switch id := identity.(type) { |
| case *spiffeID: |
| return &s2av1pb.Identity{ |
| IdentityOneof: &s2av1pb.Identity_SpiffeId{SpiffeId: id.Name()}, |
| Attributes: id.Attributes(), |
| }, nil |
| case *hostname: |
| return &s2av1pb.Identity{ |
| IdentityOneof: &s2av1pb.Identity_Hostname{Hostname: id.Name()}, |
| Attributes: id.Attributes(), |
| }, nil |
| case *uid: |
| return &s2av1pb.Identity{ |
| IdentityOneof: &s2av1pb.Identity_Uid{Uid: id.Name()}, |
| Attributes: id.Attributes(), |
| }, nil |
| case *UnspecifiedID: |
| return &s2av1pb.Identity{ |
| Attributes: id.Attributes(), |
| }, nil |
| default: |
| return nil, errors.New("unrecognized identity type") |
| } |
| } |
| |
| func toV2ProtoIdentity(identity Identity) (*s2apb.Identity, error) { |
| if identity == nil { |
| return nil, nil |
| } |
| switch id := identity.(type) { |
| case *spiffeID: |
| return &s2apb.Identity{ |
| IdentityOneof: &s2apb.Identity_SpiffeId{SpiffeId: id.Name()}, |
| Attributes: id.Attributes(), |
| }, nil |
| case *hostname: |
| return &s2apb.Identity{ |
| IdentityOneof: &s2apb.Identity_Hostname{Hostname: id.Name()}, |
| Attributes: id.Attributes(), |
| }, nil |
| case *uid: |
| return &s2apb.Identity{ |
| IdentityOneof: &s2apb.Identity_Uid{Uid: id.Name()}, |
| Attributes: id.Attributes(), |
| }, nil |
| case *UnspecifiedID: |
| return &s2apb.Identity{ |
| Attributes: id.Attributes(), |
| }, nil |
| default: |
| return nil, errors.New("unrecognized identity type") |
| } |
| } |