mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-09 15:17:39 +02:00
153 lines
4.6 KiB
Go
153 lines
4.6 KiB
Go
package authenticator // import "github.com/pomerium/pomerium/proxy/authenticator"
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/ptypes"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
"github.com/pomerium/pomerium/internal/log"
|
|
"github.com/pomerium/pomerium/internal/middleware"
|
|
pb "github.com/pomerium/pomerium/proto/authenticate"
|
|
)
|
|
|
|
// NewGRPC returns a new authenticate service client.
|
|
func NewGRPC(opts *Options) (p Authenticator, err error) {
|
|
// gRPC uses a pre-shared secret middleware to establish authentication b/w server and client
|
|
if opts.SharedSecret == "" {
|
|
return nil, errors.New("proxy/authenticator: grpc client requires shared secret")
|
|
}
|
|
grpcAuth := middleware.NewSharedSecretCred(opts.SharedSecret)
|
|
|
|
var connAddr string
|
|
if opts.InternalAddr != "" {
|
|
connAddr = opts.InternalAddr
|
|
} else {
|
|
connAddr = opts.Addr
|
|
}
|
|
if connAddr == "" {
|
|
return nil, errors.New("proxy/authenticator: connection address required")
|
|
}
|
|
// no colon exists in the connection string, assume one must be added manually
|
|
if !strings.Contains(":", connAddr) {
|
|
connAddr = fmt.Sprintf("%s:%d", connAddr, opts.Port)
|
|
}
|
|
|
|
cp, err := x509.SystemCertPool()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Info().
|
|
Str("OverrideCertificateName", opts.OverrideCertificateName).
|
|
Str("addr", connAddr).Msgf("proxy/authenticator: grpc connection")
|
|
cert := credentials.NewTLS(&tls.Config{RootCAs: cp})
|
|
|
|
// override allowed certificate name string, typically used when doing behind ingress connection
|
|
if opts.OverrideCertificateName != "" {
|
|
err = cert.OverrideServerName(opts.OverrideCertificateName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
conn, err := grpc.Dial(
|
|
connAddr,
|
|
grpc.WithTransportCredentials(cert),
|
|
grpc.WithPerRPCCredentials(grpcAuth),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
authClient := pb.NewAuthenticatorClient(conn)
|
|
return &AuthenticateGRPC{conn: conn, client: authClient}, nil
|
|
}
|
|
|
|
// RedeemResponse contains data from a authenticator redeem request.
|
|
type RedeemResponse struct {
|
|
AccessToken string
|
|
RefreshToken string
|
|
IDToken string
|
|
User string
|
|
Email string
|
|
Expiry time.Time
|
|
}
|
|
|
|
// AuthenticateGRPC is a gRPC implementation of an authenticator (authenticate client)
|
|
type AuthenticateGRPC struct {
|
|
conn *grpc.ClientConn
|
|
client pb.AuthenticatorClient
|
|
}
|
|
|
|
// Redeem makes an RPC call to the authenticate service to creates a session state
|
|
// from an encrypted code provided as a result of an oauth2 callback process.
|
|
func (a *AuthenticateGRPC) Redeem(code string) (*RedeemResponse, error) {
|
|
if code == "" {
|
|
return nil, errors.New("missing code")
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
r, err := a.client.Authenticate(ctx, &pb.AuthenticateRequest{Code: code})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
expiry, err := ptypes.Timestamp(r.Expiry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &RedeemResponse{
|
|
AccessToken: r.AccessToken,
|
|
RefreshToken: r.RefreshToken,
|
|
IDToken: r.IdToken,
|
|
User: r.User,
|
|
Email: r.Email,
|
|
Expiry: expiry,
|
|
}, nil
|
|
}
|
|
|
|
// Refresh makes an RPC call to the authenticate service to attempt to refresh the
|
|
// user's session. Requires a valid refresh token. Will return an error if the identity provider
|
|
// has revoked the session or if the refresh token is no longer valid in this context.
|
|
func (a *AuthenticateGRPC) Refresh(refreshToken string) (string, time.Time, error) {
|
|
if refreshToken == "" {
|
|
return "", time.Time{}, errors.New("missing refresh token")
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
r, err := a.client.Refresh(ctx, &pb.RefreshRequest{RefreshToken: refreshToken})
|
|
if err != nil {
|
|
return "", time.Time{}, err
|
|
}
|
|
|
|
expiry, err := ptypes.Timestamp(r.Expiry)
|
|
if err != nil {
|
|
return "", time.Time{}, err
|
|
}
|
|
return r.AccessToken, expiry, nil
|
|
}
|
|
|
|
// Validate makes an RPC call to the authenticate service to validate the JWT id token;
|
|
// does NOT do nonce or revokation validation.
|
|
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
|
func (a *AuthenticateGRPC) Validate(idToken string) (bool, error) {
|
|
if idToken == "" {
|
|
return false, errors.New("missing id token")
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
r, err := a.client.Validate(ctx, &pb.ValidateRequest{IdToken: idToken})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return r.IsValid, nil
|
|
}
|
|
|
|
// Close tears down the ClientConn and all underlying connections.
|
|
func (a *AuthenticateGRPC) Close() error {
|
|
return a.conn.Close()
|
|
}
|