| /* | |
| * | |
| * 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") | |
| } | |
| } |