mirror of
https://github.com/pomerium/pomerium.git
synced 2025-07-18 00:58:08 +02:00
identity: add support for verifying access and identity tokens
This commit is contained in:
parent
3043e98fab
commit
4d04838ebd
18 changed files with 1126 additions and 609 deletions
|
@ -3,11 +3,12 @@ package authenticate
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
oteltrace "go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/urlutil"
|
"github.com/pomerium/pomerium/internal/urlutil"
|
||||||
"github.com/pomerium/pomerium/pkg/identity"
|
"github.com/pomerium/pomerium/pkg/identity"
|
||||||
"github.com/pomerium/pomerium/pkg/identity/oauth"
|
"github.com/pomerium/pomerium/pkg/identity/oauth"
|
||||||
oteltrace "go.opentelemetry.io/otel/trace"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaultGetIdentityProvider(ctx context.Context, tracerProvider oteltrace.TracerProvider, options *config.Options, idpID string) (identity.Authenticator, error) {
|
func defaultGetIdentityProvider(ctx context.Context, tracerProvider oteltrace.TracerProvider, options *config.Options, idpID string) (identity.Authenticator, error) {
|
||||||
|
@ -26,7 +27,8 @@ func defaultGetIdentityProvider(ctx context.Context, tracerProvider oteltrace.Tr
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return identity.NewAuthenticator(ctx, tracerProvider, oauth.Options{
|
|
||||||
|
o := oauth.Options{
|
||||||
RedirectURL: redirectURL,
|
RedirectURL: redirectURL,
|
||||||
ProviderName: idp.GetType(),
|
ProviderName: idp.GetType(),
|
||||||
ProviderURL: idp.GetUrl(),
|
ProviderURL: idp.GetUrl(),
|
||||||
|
@ -34,5 +36,9 @@ func defaultGetIdentityProvider(ctx context.Context, tracerProvider oteltrace.Tr
|
||||||
ClientSecret: idp.GetClientSecret(),
|
ClientSecret: idp.GetClientSecret(),
|
||||||
Scopes: idp.GetScopes(),
|
Scopes: idp.GetScopes(),
|
||||||
AuthCodeOptions: idp.GetRequestParams(),
|
AuthCodeOptions: idp.GetRequestParams(),
|
||||||
})
|
}
|
||||||
|
if v := idp.GetAccessTokenAllowedAudiences(); v != nil {
|
||||||
|
o.AccessTokenAllowedAudiences = &v.Values
|
||||||
|
}
|
||||||
|
return identity.NewAuthenticator(ctx, tracerProvider, o)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/urlutil"
|
"github.com/pomerium/pomerium/internal/urlutil"
|
||||||
"github.com/pomerium/pomerium/pkg/grpc/identity"
|
"github.com/pomerium/pomerium/pkg/grpc/identity"
|
||||||
)
|
)
|
||||||
|
@ -43,6 +45,11 @@ func (o *Options) GetIdentityProviderForPolicy(policy *Policy) (*identity.Provid
|
||||||
Url: o.ProviderURL,
|
Url: o.ProviderURL,
|
||||||
RequestParams: o.RequestParams,
|
RequestParams: o.RequestParams,
|
||||||
}
|
}
|
||||||
|
if v := o.IDPAccessTokenAllowedAudiences; v != nil {
|
||||||
|
idp.AccessTokenAllowedAudiences = &identity.Provider_StringList{
|
||||||
|
Values: slices.Clone(*v),
|
||||||
|
}
|
||||||
|
}
|
||||||
if policy != nil {
|
if policy != nil {
|
||||||
if policy.IDPClientID != "" {
|
if policy.IDPClientID != "" {
|
||||||
idp.ClientId = policy.IDPClientID
|
idp.ClientId = policy.IDPClientID
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -152,12 +153,13 @@ type Options struct {
|
||||||
|
|
||||||
// Identity provider configuration variables as specified by RFC6749
|
// Identity provider configuration variables as specified by RFC6749
|
||||||
// https://openid.net/specs/openid-connect-basic-1_0.html#RFC6749
|
// https://openid.net/specs/openid-connect-basic-1_0.html#RFC6749
|
||||||
ClientID string `mapstructure:"idp_client_id" yaml:"idp_client_id,omitempty"`
|
ClientID string `mapstructure:"idp_client_id" yaml:"idp_client_id,omitempty"`
|
||||||
ClientSecret string `mapstructure:"idp_client_secret" yaml:"idp_client_secret,omitempty"`
|
ClientSecret string `mapstructure:"idp_client_secret" yaml:"idp_client_secret,omitempty"`
|
||||||
ClientSecretFile string `mapstructure:"idp_client_secret_file" yaml:"idp_client_secret_file,omitempty"`
|
ClientSecretFile string `mapstructure:"idp_client_secret_file" yaml:"idp_client_secret_file,omitempty"`
|
||||||
Provider string `mapstructure:"idp_provider" yaml:"idp_provider,omitempty"`
|
Provider string `mapstructure:"idp_provider" yaml:"idp_provider,omitempty"`
|
||||||
ProviderURL string `mapstructure:"idp_provider_url" yaml:"idp_provider_url,omitempty"`
|
ProviderURL string `mapstructure:"idp_provider_url" yaml:"idp_provider_url,omitempty"`
|
||||||
Scopes []string `mapstructure:"idp_scopes" yaml:"idp_scopes,omitempty"`
|
Scopes []string `mapstructure:"idp_scopes" yaml:"idp_scopes,omitempty"`
|
||||||
|
IDPAccessTokenAllowedAudiences *[]string `mapstructure:"idp_access_token_allowed_audiences" yaml:"idp_access_token_allowed_audiences,omitempty"`
|
||||||
|
|
||||||
// RequestParams are custom request params added to the signin request as
|
// RequestParams are custom request params added to the signin request as
|
||||||
// part of an Oauth2 code flow.
|
// part of an Oauth2 code flow.
|
||||||
|
@ -1487,6 +1489,12 @@ func (o *Options) ApplySettings(ctx context.Context, certsIndex *cryptutil.Certi
|
||||||
set(&o.ProviderURL, settings.IdpProviderUrl)
|
set(&o.ProviderURL, settings.IdpProviderUrl)
|
||||||
setSlice(&o.Scopes, settings.Scopes)
|
setSlice(&o.Scopes, settings.Scopes)
|
||||||
setMap(&o.RequestParams, settings.RequestParams)
|
setMap(&o.RequestParams, settings.RequestParams)
|
||||||
|
if settings.IdpAccessTokenAllowedAudiences != nil {
|
||||||
|
values := slices.Clone(settings.IdpAccessTokenAllowedAudiences.Values)
|
||||||
|
o.IDPAccessTokenAllowedAudiences = &values
|
||||||
|
} else {
|
||||||
|
o.IDPAccessTokenAllowedAudiences = nil
|
||||||
|
}
|
||||||
setSlice(&o.AuthorizeURLStrings, settings.AuthorizeServiceUrls)
|
setSlice(&o.AuthorizeURLStrings, settings.AuthorizeServiceUrls)
|
||||||
set(&o.AuthorizeInternalURLString, settings.AuthorizeInternalServiceUrl)
|
set(&o.AuthorizeInternalURLString, settings.AuthorizeInternalServiceUrl)
|
||||||
set(&o.OverrideCertificateName, settings.OverrideCertificateName)
|
set(&o.OverrideCertificateName, settings.OverrideCertificateName)
|
||||||
|
@ -1591,6 +1599,13 @@ func (o *Options) ToProto() *config.Config {
|
||||||
copySrcToOptionalDest(&settings.IdpProviderUrl, &o.ProviderURL)
|
copySrcToOptionalDest(&settings.IdpProviderUrl, &o.ProviderURL)
|
||||||
settings.Scopes = o.Scopes
|
settings.Scopes = o.Scopes
|
||||||
settings.RequestParams = o.RequestParams
|
settings.RequestParams = o.RequestParams
|
||||||
|
if o.IDPAccessTokenAllowedAudiences != nil {
|
||||||
|
settings.IdpAccessTokenAllowedAudiences = &config.Settings_StringList{
|
||||||
|
Values: slices.Clone(*o.IDPAccessTokenAllowedAudiences),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
settings.IdpAccessTokenAllowedAudiences = nil
|
||||||
|
}
|
||||||
settings.AuthorizeServiceUrls = o.AuthorizeURLStrings
|
settings.AuthorizeServiceUrls = o.AuthorizeURLStrings
|
||||||
copySrcToOptionalDest(&settings.AuthorizeInternalServiceUrl, &o.AuthorizeInternalURLString)
|
copySrcToOptionalDest(&settings.AuthorizeInternalServiceUrl, &o.AuthorizeInternalURLString)
|
||||||
copySrcToOptionalDest(&settings.OverrideCertificateName, &o.OverrideCertificateName)
|
copySrcToOptionalDest(&settings.OverrideCertificateName, &o.OverrideCertificateName)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -186,6 +187,8 @@ type Policy struct {
|
||||||
IDPClientID string `mapstructure:"idp_client_id" yaml:"idp_client_id,omitempty"`
|
IDPClientID string `mapstructure:"idp_client_id" yaml:"idp_client_id,omitempty"`
|
||||||
// IDPClientSecret is the client secret used for the identity provider.
|
// IDPClientSecret is the client secret used for the identity provider.
|
||||||
IDPClientSecret string `mapstructure:"idp_client_secret" yaml:"idp_client_secret,omitempty"`
|
IDPClientSecret string `mapstructure:"idp_client_secret" yaml:"idp_client_secret,omitempty"`
|
||||||
|
// IDPAccessTokenAllowedAudiences are the allowed audiences for idp access token validation.
|
||||||
|
IDPAccessTokenAllowedAudiences *[]string `mapstructure:"idp_access_token_allowed_audiences" yaml:"idp_access_token_allowed_audiences,omitempty"`
|
||||||
|
|
||||||
// ShowErrorDetails indicates whether or not additional error details should be displayed.
|
// ShowErrorDetails indicates whether or not additional error details should be displayed.
|
||||||
ShowErrorDetails bool `mapstructure:"show_error_details" yaml:"show_error_details" json:"show_error_details"`
|
ShowErrorDetails bool `mapstructure:"show_error_details" yaml:"show_error_details" json:"show_error_details"`
|
||||||
|
@ -332,6 +335,12 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
|
||||||
TLSUpstreamServerName: pb.GetTlsUpstreamServerName(),
|
TLSUpstreamServerName: pb.GetTlsUpstreamServerName(),
|
||||||
UpstreamTimeout: timeout,
|
UpstreamTimeout: timeout,
|
||||||
}
|
}
|
||||||
|
if pb.IdpAccessTokenAllowedAudiences != nil {
|
||||||
|
values := slices.Clone(pb.IdpAccessTokenAllowedAudiences.Values)
|
||||||
|
p.IDPAccessTokenAllowedAudiences = &values
|
||||||
|
} else {
|
||||||
|
p.IDPAccessTokenAllowedAudiences = nil
|
||||||
|
}
|
||||||
if pb.Redirect.IsSet() {
|
if pb.Redirect.IsSet() {
|
||||||
p.Redirect = &PolicyRedirect{
|
p.Redirect = &PolicyRedirect{
|
||||||
HTTPSRedirect: pb.Redirect.HttpsRedirect,
|
HTTPSRedirect: pb.Redirect.HttpsRedirect,
|
||||||
|
@ -505,6 +514,13 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
|
||||||
if p.IDPClientSecret != "" {
|
if p.IDPClientSecret != "" {
|
||||||
pb.IdpClientSecret = proto.String(p.IDPClientSecret)
|
pb.IdpClientSecret = proto.String(p.IDPClientSecret)
|
||||||
}
|
}
|
||||||
|
if p.IDPAccessTokenAllowedAudiences != nil {
|
||||||
|
pb.IdpAccessTokenAllowedAudiences = &configpb.Route_StringList{
|
||||||
|
Values: slices.Clone(*p.IDPAccessTokenAllowedAudiences),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pb.IdpAccessTokenAllowedAudiences = nil
|
||||||
|
}
|
||||||
if p.Redirect != nil {
|
if p.Redirect != nil {
|
||||||
pb.Redirect = &configpb.RouteRedirect{
|
pb.Redirect = &configpb.RouteRedirect{
|
||||||
HttpsRedirect: p.Redirect.HTTPSRedirect,
|
HttpsRedirect: p.Redirect.HTTPSRedirect,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -45,8 +45,10 @@ enum IssuerFormat {
|
||||||
IssuerURI = 1;
|
IssuerURI = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next ID: 69.
|
// Next ID: 70.
|
||||||
message Route {
|
message Route {
|
||||||
|
message StringList { repeated string values = 1; }
|
||||||
|
|
||||||
string name = 1;
|
string name = 1;
|
||||||
string description = 67;
|
string description = 67;
|
||||||
string logo_url = 68;
|
string logo_url = 68;
|
||||||
|
@ -130,6 +132,7 @@ message Route {
|
||||||
|
|
||||||
optional string idp_client_id = 55;
|
optional string idp_client_id = 55;
|
||||||
optional string idp_client_secret = 56;
|
optional string idp_client_secret = 56;
|
||||||
|
optional StringList idp_access_token_allowed_audiences = 69;
|
||||||
bool show_error_details = 59;
|
bool show_error_details = 59;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +152,7 @@ message Policy {
|
||||||
string remediation = 9;
|
string remediation = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next ID: 137.
|
// Next ID: 138.
|
||||||
message Settings {
|
message Settings {
|
||||||
message Certificate {
|
message Certificate {
|
||||||
bytes cert_bytes = 3;
|
bytes cert_bytes = 3;
|
||||||
|
@ -188,6 +191,7 @@ message Settings {
|
||||||
optional string idp_client_secret = 23;
|
optional string idp_client_secret = 23;
|
||||||
optional string idp_provider = 24;
|
optional string idp_provider = 24;
|
||||||
optional string idp_provider_url = 25;
|
optional string idp_provider_url = 25;
|
||||||
|
optional StringList idp_access_token_allowed_audiences = 137;
|
||||||
repeated string scopes = 26;
|
repeated string scopes = 26;
|
||||||
// optional string idp_service_account = 27;
|
// optional string idp_service_account = 27;
|
||||||
// optional google.protobuf.Duration idp_refresh_directory_timeout = 28;
|
// optional google.protobuf.Duration idp_refresh_directory_timeout = 28;
|
||||||
|
|
|
@ -33,8 +33,9 @@ type Provider struct {
|
||||||
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
|
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
|
||||||
Scopes []string `protobuf:"bytes,5,rep,name=scopes,proto3" json:"scopes,omitempty"`
|
Scopes []string `protobuf:"bytes,5,rep,name=scopes,proto3" json:"scopes,omitempty"`
|
||||||
// string service_account = 6;
|
// string service_account = 6;
|
||||||
Url string `protobuf:"bytes,7,opt,name=url,proto3" json:"url,omitempty"`
|
Url string `protobuf:"bytes,7,opt,name=url,proto3" json:"url,omitempty"`
|
||||||
RequestParams map[string]string `protobuf:"bytes,8,rep,name=request_params,json=requestParams,proto3" json:"request_params,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
RequestParams map[string]string `protobuf:"bytes,8,rep,name=request_params,json=requestParams,proto3" json:"request_params,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||||
|
AccessTokenAllowedAudiences *Provider_StringList `protobuf:"bytes,10,opt,name=access_token_allowed_audiences,json=accessTokenAllowedAudiences,proto3,oneof" json:"access_token_allowed_audiences,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Provider) Reset() {
|
func (x *Provider) Reset() {
|
||||||
|
@ -125,6 +126,13 @@ func (x *Provider) GetRequestParams() map[string]string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Provider) GetAccessTokenAllowedAudiences() *Provider_StringList {
|
||||||
|
if x != nil {
|
||||||
|
return x.AccessTokenAllowedAudiences
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
@ -196,6 +204,53 @@ func (x *Profile) GetClaims() *structpb.Struct {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Provider_StringList struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Provider_StringList) Reset() {
|
||||||
|
*x = Provider_StringList{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_identity_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Provider_StringList) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Provider_StringList) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Provider_StringList) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_identity_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Provider_StringList.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Provider_StringList) Descriptor() ([]byte, []int) {
|
||||||
|
return file_identity_proto_rawDescGZIP(), []int{0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Provider_StringList) GetValues() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Values
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_identity_proto protoreflect.FileDescriptor
|
var File_identity_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_identity_proto_rawDesc = []byte{
|
var file_identity_proto_rawDesc = []byte{
|
||||||
|
@ -203,7 +258,7 @@ var file_identity_proto_rawDesc = []byte{
|
||||||
0x12, 0x11, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74,
|
0x12, 0x11, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74,
|
||||||
0x69, 0x74, 0x79, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
0x69, 0x74, 0x79, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||||
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||||
0x6f, 0x22, 0xed, 0x02, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e,
|
0x6f, 0x22, 0xa8, 0x04, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e,
|
||||||
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x38,
|
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x38,
|
||||||
0x0a, 0x18, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x73,
|
0x0a, 0x18, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x73,
|
||||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09,
|
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09,
|
||||||
|
@ -221,25 +276,36 @@ var file_identity_proto_rawDesc = []byte{
|
||||||
0x32, 0x2e, 0x2e, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x69, 0x64, 0x65, 0x6e,
|
0x32, 0x2e, 0x2e, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x69, 0x64, 0x65, 0x6e,
|
||||||
0x74, 0x69, 0x74, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x52, 0x65,
|
0x74, 0x69, 0x74, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x52, 0x65,
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
|
||||||
0x52, 0x0d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a,
|
0x52, 0x0d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12,
|
||||||
0x40, 0x0a, 0x12, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73,
|
0x70, 0x0a, 0x1e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f,
|
||||||
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
|
0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65,
|
||||||
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
|
0x75, 0x6d, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x76,
|
||||||
0x01, 0x22, 0x97, 0x01, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1f, 0x0a,
|
0x69, 0x64, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x48,
|
||||||
0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
0x00, 0x52, 0x1b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x41, 0x6c,
|
||||||
0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19,
|
0x6c, 0x6f, 0x77, 0x65, 0x64, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x88, 0x01,
|
||||||
0x0a, 0x08, 0x69, 0x64, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,
|
0x01, 0x1a, 0x24, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12,
|
||||||
0x52, 0x07, 0x69, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x61, 0x75,
|
0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52,
|
||||||
0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a,
|
0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
0x6f, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2f, 0x0a, 0x06, 0x63, 0x6c,
|
0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
|
||||||
0x61, 0x69, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f,
|
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
|
||||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
|
0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
|
||||||
0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x42, 0x30, 0x5a, 0x2e, 0x67,
|
0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x21, 0x0a, 0x1f, 0x5f, 0x61, 0x63,
|
||||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69,
|
0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77,
|
||||||
0x75, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6b, 0x67, 0x2f,
|
0x65, 0x64, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x97, 0x01, 0x0a,
|
||||||
0x67, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70,
|
0x07, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x76,
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70,
|
||||||
|
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x64, 0x5f,
|
||||||
|
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x69, 0x64, 0x54,
|
||||||
|
0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f,
|
||||||
|
0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6f, 0x61, 0x75, 0x74, 0x68,
|
||||||
|
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2f, 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18,
|
||||||
|
0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06,
|
||||||
|
0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||||
|
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6f,
|
||||||
|
0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f,
|
||||||
|
0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -254,21 +320,23 @@ func file_identity_proto_rawDescGZIP() []byte {
|
||||||
return file_identity_proto_rawDescData
|
return file_identity_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_identity_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
var file_identity_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||||
var file_identity_proto_goTypes = []any{
|
var file_identity_proto_goTypes = []any{
|
||||||
(*Provider)(nil), // 0: pomerium.identity.Provider
|
(*Provider)(nil), // 0: pomerium.identity.Provider
|
||||||
(*Profile)(nil), // 1: pomerium.identity.Profile
|
(*Profile)(nil), // 1: pomerium.identity.Profile
|
||||||
nil, // 2: pomerium.identity.Provider.RequestParamsEntry
|
(*Provider_StringList)(nil), // 2: pomerium.identity.Provider.StringList
|
||||||
(*structpb.Struct)(nil), // 3: google.protobuf.Struct
|
nil, // 3: pomerium.identity.Provider.RequestParamsEntry
|
||||||
|
(*structpb.Struct)(nil), // 4: google.protobuf.Struct
|
||||||
}
|
}
|
||||||
var file_identity_proto_depIdxs = []int32{
|
var file_identity_proto_depIdxs = []int32{
|
||||||
2, // 0: pomerium.identity.Provider.request_params:type_name -> pomerium.identity.Provider.RequestParamsEntry
|
3, // 0: pomerium.identity.Provider.request_params:type_name -> pomerium.identity.Provider.RequestParamsEntry
|
||||||
3, // 1: pomerium.identity.Profile.claims:type_name -> google.protobuf.Struct
|
2, // 1: pomerium.identity.Provider.access_token_allowed_audiences:type_name -> pomerium.identity.Provider.StringList
|
||||||
2, // [2:2] is the sub-list for method output_type
|
4, // 2: pomerium.identity.Profile.claims:type_name -> google.protobuf.Struct
|
||||||
2, // [2:2] is the sub-list for method input_type
|
3, // [3:3] is the sub-list for method output_type
|
||||||
2, // [2:2] is the sub-list for extension type_name
|
3, // [3:3] is the sub-list for method input_type
|
||||||
2, // [2:2] is the sub-list for extension extendee
|
3, // [3:3] is the sub-list for extension type_name
|
||||||
0, // [0:2] is the sub-list for field type_name
|
3, // [3:3] is the sub-list for extension extendee
|
||||||
|
0, // [0:3] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_identity_proto_init() }
|
func init() { file_identity_proto_init() }
|
||||||
|
@ -301,14 +369,27 @@ func file_identity_proto_init() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file_identity_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*Provider_StringList); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
file_identity_proto_msgTypes[0].OneofWrappers = []any{}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_identity_proto_rawDesc,
|
RawDescriptor: file_identity_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 3,
|
NumMessages: 4,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,6 +6,7 @@ option go_package = "github.com/pomerium/pomerium/pkg/grpc/identity";
|
||||||
import "google/protobuf/struct.proto";
|
import "google/protobuf/struct.proto";
|
||||||
|
|
||||||
message Provider {
|
message Provider {
|
||||||
|
message StringList { repeated string values = 1; }
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string authenticate_service_url = 9;
|
string authenticate_service_url = 9;
|
||||||
string client_id = 2;
|
string client_id = 2;
|
||||||
|
@ -15,6 +16,7 @@ message Provider {
|
||||||
// string service_account = 6;
|
// string service_account = 6;
|
||||||
string url = 7;
|
string url = 7;
|
||||||
map<string, string> request_params = 8;
|
map<string, string> request_params = 8;
|
||||||
|
optional StringList access_token_allowed_audiences = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Profile {
|
message Profile {
|
||||||
|
|
9
pkg/identity/errors.go
Normal file
9
pkg/identity/errors.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package identity
|
||||||
|
|
||||||
|
import "github.com/pomerium/pomerium/pkg/identity/identity"
|
||||||
|
|
||||||
|
// re-exported errors
|
||||||
|
var (
|
||||||
|
ErrVerifyAccessTokenNotSupported = identity.ErrVerifyAccessTokenNotSupported
|
||||||
|
ErrVerifyIdentityTokenNotSupported = identity.ErrVerifyIdentityTokenNotSupported
|
||||||
|
)
|
9
pkg/identity/identity/errors.go
Normal file
9
pkg/identity/identity/errors.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package identity
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// well known errors
|
||||||
|
var (
|
||||||
|
ErrVerifyAccessTokenNotSupported = errors.New("identity: access token verification not supported")
|
||||||
|
ErrVerifyIdentityTokenNotSupported = errors.New("identity: identity token verification not supported")
|
||||||
|
)
|
|
@ -2,6 +2,7 @@ package identity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
@ -55,3 +56,13 @@ func (mp MockProvider) SignOut(_ http.ResponseWriter, _ *http.Request, _, _, _ s
|
||||||
func (mp MockProvider) SignIn(_ http.ResponseWriter, _ *http.Request, _ string) error {
|
func (mp MockProvider) SignIn(_ http.ResponseWriter, _ *http.Request, _ string) error {
|
||||||
return mp.SignInError
|
return mp.SignInError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyAccessToken verifies an access token.
|
||||||
|
func (mp MockProvider) VerifyAccessToken(_ context.Context, _ string) (claims map[string]any, err error) {
|
||||||
|
return nil, fmt.Errorf("VerifyAccessToken not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyIdentityToken verifies an identity token.
|
||||||
|
func (mp MockProvider) VerifyIdentityToken(_ context.Context, _ string) (claims map[string]any, err error) {
|
||||||
|
return nil, fmt.Errorf("VerifyIdentityToken not implemented")
|
||||||
|
}
|
||||||
|
|
|
@ -182,3 +182,13 @@ func (p *Provider) SignIn(w http.ResponseWriter, r *http.Request, state string)
|
||||||
func (p *Provider) SignOut(_ http.ResponseWriter, _ *http.Request, _, _, _ string) error {
|
func (p *Provider) SignOut(_ http.ResponseWriter, _ *http.Request, _, _, _ string) error {
|
||||||
return oidc.ErrSignoutNotImplemented
|
return oidc.ErrSignoutNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyAccessToken verifies an access token.
|
||||||
|
func (p *Provider) VerifyAccessToken(_ context.Context, _ string) (claims map[string]any, err error) {
|
||||||
|
return nil, identity.ErrVerifyAccessTokenNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyIdentityToken verifies an identity token.
|
||||||
|
func (p *Provider) VerifyIdentityToken(_ context.Context, _ string) (claims map[string]any, err error) {
|
||||||
|
return nil, identity.ErrVerifyIdentityTokenNotSupported
|
||||||
|
}
|
||||||
|
|
|
@ -256,3 +256,13 @@ func (p *Provider) SignIn(w http.ResponseWriter, r *http.Request, state string)
|
||||||
func (p *Provider) SignOut(_ http.ResponseWriter, _ *http.Request, _, _, _ string) error {
|
func (p *Provider) SignOut(_ http.ResponseWriter, _ *http.Request, _, _, _ string) error {
|
||||||
return oidc.ErrSignoutNotImplemented
|
return oidc.ErrSignoutNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyAccessToken verifies an access token.
|
||||||
|
func (p *Provider) VerifyAccessToken(_ context.Context, _ string) (claims map[string]any, err error) {
|
||||||
|
return nil, identity.ErrVerifyAccessTokenNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyIdentityToken verifies an identity token.
|
||||||
|
func (p *Provider) VerifyIdentityToken(_ context.Context, _ string) (claims map[string]any, err error) {
|
||||||
|
return nil, identity.ErrVerifyIdentityTokenNotSupported
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
// authorization with Bearer JWT.
|
// authorization with Bearer JWT.
|
||||||
package oauth
|
package oauth
|
||||||
|
|
||||||
import "net/url"
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
// Options contains the fields required for an OAuth 2.0 (inc. OIDC) auth flow.
|
// Options contains the fields required for an OAuth 2.0 (inc. OIDC) auth flow.
|
||||||
//
|
//
|
||||||
|
@ -29,4 +31,7 @@ type Options struct {
|
||||||
// AuthCodeOptions specifies additional key value pairs query params to add
|
// AuthCodeOptions specifies additional key value pairs query params to add
|
||||||
// to the request flow signin url.
|
// to the request flow signin url.
|
||||||
AuthCodeOptions map[string]string
|
AuthCodeOptions map[string]string
|
||||||
|
|
||||||
|
// When set validates the audience in access tokens.
|
||||||
|
AccessTokenAllowedAudiences *[]string
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
go_oidc "github.com/coreos/go-oidc/v3/oidc"
|
go_oidc "github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"github.com/google/uuid"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/pkg/identity/oauth"
|
"github.com/pomerium/pomerium/pkg/identity/oauth"
|
||||||
|
@ -37,11 +40,13 @@ var defaultAuthCodeOptions = map[string]string{"prompt": "select_account"}
|
||||||
// Provider is an Azure implementation of the Authenticator interface.
|
// Provider is an Azure implementation of the Authenticator interface.
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
*pom_oidc.Provider
|
*pom_oidc.Provider
|
||||||
|
accessTokenAllowedAudiences *[]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New instantiates an OpenID Connect (OIDC) provider for Azure.
|
// New instantiates an OpenID Connect (OIDC) provider for Azure.
|
||||||
func New(ctx context.Context, o *oauth.Options) (*Provider, error) {
|
func New(ctx context.Context, o *oauth.Options) (*Provider, error) {
|
||||||
var p Provider
|
var p Provider
|
||||||
|
p.accessTokenAllowedAudiences = o.AccessTokenAllowedAudiences
|
||||||
var err error
|
var err error
|
||||||
if o.ProviderURL == "" {
|
if o.ProviderURL == "" {
|
||||||
o.ProviderURL = defaultProviderURL
|
o.ProviderURL = defaultProviderURL
|
||||||
|
@ -73,6 +78,59 @@ func (p *Provider) Name() string {
|
||||||
return Name
|
return Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyAccessToken verifies a raw access token.
|
||||||
|
func (p *Provider) VerifyAccessToken(ctx context.Context, rawAccessToken string) (claims map[string]any, err error) {
|
||||||
|
pp, err := p.GetProvider()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting oidc provider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// azure access tokens are JWTs signed with the same keys as identity tokens
|
||||||
|
verifier := pp.Verifier(&go_oidc.Config{
|
||||||
|
SkipClientIDCheck: true,
|
||||||
|
SkipIssuerCheck: true, // checked later
|
||||||
|
})
|
||||||
|
token, err := verifier.Verify(ctx, rawAccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error verifying access token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
claims = map[string]any{}
|
||||||
|
err = token.Claims(&claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error unmarshaling access token claims: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify audience
|
||||||
|
if p.accessTokenAllowedAudiences != nil {
|
||||||
|
if audience, ok := claims["aud"].(string); !ok || !slices.Contains(*p.accessTokenAllowedAudiences, audience) {
|
||||||
|
return nil, fmt.Errorf("error verifying access token audience claim, invalid audience")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = verifyIssuer(pp, claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error verifying access token issuer claim: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if scope, ok := claims["scp"].(string); ok && slices.Contains(strings.Fields(scope), "openid") {
|
||||||
|
userInfo, err := pp.UserInfo(ctx, oauth2.StaticTokenSource(&oauth2.Token{
|
||||||
|
TokenType: "Bearer",
|
||||||
|
AccessToken: rawAccessToken,
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error calling user info endpoint: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userInfo.Claims(claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error unmarshaling user info claims: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
// newProvider overrides the default round tripper for well-known endpoint call that happens
|
// newProvider overrides the default round tripper for well-known endpoint call that happens
|
||||||
// on new provider registration.
|
// on new provider registration.
|
||||||
// By default, the "common" (both public and private domains) responds with
|
// By default, the "common" (both public and private domains) responds with
|
||||||
|
@ -128,3 +186,55 @@ func (transport *wellKnownConfiguration) RoundTrip(req *http.Request) (*http.Res
|
||||||
res.Body = io.NopCloser(bytes.NewReader(bs))
|
res.Body = io.NopCloser(bytes.NewReader(bs))
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
v1IssuerPrefix = "https://sts.windows.net/"
|
||||||
|
v1IssuerSuffix = "/"
|
||||||
|
v2IssuerPrefix = "https://login.microsoftonline.com/"
|
||||||
|
v2IssuerSuffix = "/v2.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
func verifyIssuer(pp *go_oidc.Provider, claims map[string]any) error {
|
||||||
|
tenantID, ok := getTenantIDFromURL(pp.Endpoint().TokenURL)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to find tenant id")
|
||||||
|
}
|
||||||
|
|
||||||
|
iss, ok := claims["iss"].(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("missing issuer claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(iss == v1IssuerPrefix+tenantID+v1IssuerSuffix || iss == v2IssuerPrefix+tenantID+v2IssuerSuffix) {
|
||||||
|
return fmt.Errorf("invalid issuer: %s", iss)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTenantIDFromURL(rawTokenURL string) (string, bool) {
|
||||||
|
// URLs look like:
|
||||||
|
// - https://login.microsoftonline.com/f42bce3b-671c-4162-b24c-00ecc7641897/v2.0
|
||||||
|
// Or:
|
||||||
|
// - https://sts.windows.net/f42bce3b-671c-4162-b24c-00ecc7641897/
|
||||||
|
for _, prefix := range []string{v1IssuerPrefix, v2IssuerPrefix} {
|
||||||
|
path, ok := strings.CutPrefix(rawTokenURL, prefix)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := strings.Index(path, "/")
|
||||||
|
if idx <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTenantID := path[:idx]
|
||||||
|
if _, err := uuid.Parse(rawTenantID); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawTenantID, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
|
@ -2,15 +2,27 @@ package azure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v3"
|
||||||
|
"github.com/go-jose/go-jose/v3/jwt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/testutil"
|
||||||
|
"github.com/pomerium/pomerium/pkg/identity/identity"
|
||||||
"github.com/pomerium/pomerium/pkg/identity/oauth"
|
"github.com/pomerium/pomerium/pkg/identity/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAuthCodeOptions(t *testing.T) {
|
func TestAuthCodeOptions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
var options oauth.Options
|
var options oauth.Options
|
||||||
p, err := New(context.Background(), &options)
|
p, err := New(context.Background(), &options)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -21,3 +33,101 @@ func TestAuthCodeOptions(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, map[string]string{}, p.AuthCodeOptions)
|
assert.Equal(t, map[string]string{}, p.AuthCodeOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVerifyAccessToken(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.GetContext(t, time.Minute)
|
||||||
|
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
jwtSigner, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: privateKey}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
iat := time.Now().Unix()
|
||||||
|
exp := iat + 3600
|
||||||
|
rawAccessToken1, err := jwt.Signed(jwtSigner).Claims(map[string]any{
|
||||||
|
"iss": "https://sts.windows.net/323b4000-7ad7-4ed3-9f4e-adee06ee8bbe/",
|
||||||
|
"aud": "https://client.example.com",
|
||||||
|
"sub": "subject",
|
||||||
|
"exp": exp,
|
||||||
|
"iat": iat,
|
||||||
|
}).CompactSerialize()
|
||||||
|
require.NoError(t, err)
|
||||||
|
rawAccessToken2, err := jwt.Signed(jwtSigner).Claims(map[string]any{
|
||||||
|
"iss": "https://sts.windows.net/323b4000-7ad7-4ed3-9f4e-adee06ee8bbe/",
|
||||||
|
"aud": "https://unexpected.example.com",
|
||||||
|
"sub": "subject",
|
||||||
|
"exp": exp,
|
||||||
|
"iat": iat,
|
||||||
|
}).CompactSerialize()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var srvURL string
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("GET /.well-known/openid-configuration", func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"issuer": srvURL,
|
||||||
|
"authorization_endpoint": srvURL + "/auth",
|
||||||
|
"token_endpoint": "https://sts.windows.net/323b4000-7ad7-4ed3-9f4e-adee06ee8bbe/token",
|
||||||
|
"jwks_uri": srvURL + "/keys",
|
||||||
|
"id_token_signing_alg_values_supported": []any{"RS256"},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
mux.HandleFunc("GET /keys", func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(jose.JSONWebKeySet{
|
||||||
|
Keys: []jose.JSONWebKey{
|
||||||
|
{Key: privateKey.Public(), Use: "sig", Algorithm: "RS256"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
srv := httptest.NewServer(mux)
|
||||||
|
srvURL = srv.URL
|
||||||
|
|
||||||
|
audiences := []string{"https://other.example.com", "https://client.example.com"}
|
||||||
|
p, err := New(ctx, &oauth.Options{
|
||||||
|
ProviderName: Name,
|
||||||
|
ProviderURL: srv.URL,
|
||||||
|
ClientID: "CLIENT_ID",
|
||||||
|
ClientSecret: "CLIENT_SECRET",
|
||||||
|
AccessTokenAllowedAudiences: &audiences,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
claims, err := p.VerifyAccessToken(ctx, rawAccessToken1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
delete(claims, "iat")
|
||||||
|
delete(claims, "exp")
|
||||||
|
assert.Equal(t, map[string]any{
|
||||||
|
"iss": "https://sts.windows.net/323b4000-7ad7-4ed3-9f4e-adee06ee8bbe/",
|
||||||
|
"aud": "https://client.example.com",
|
||||||
|
"sub": "subject",
|
||||||
|
}, claims)
|
||||||
|
|
||||||
|
_, err = p.VerifyAccessToken(ctx, rawAccessToken2)
|
||||||
|
assert.ErrorContains(t, err, "invalid audience")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyIdentityToken(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.GetContext(t, time.Minute)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
srv := httptest.NewServer(mux)
|
||||||
|
|
||||||
|
p, err := New(ctx, &oauth.Options{
|
||||||
|
ProviderName: Name,
|
||||||
|
ProviderURL: srv.URL,
|
||||||
|
ClientID: "CLIENT_ID",
|
||||||
|
ClientSecret: "CLIENT_SECRET",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
claims, err := p.VerifyIdentityToken(ctx, "RAW IDENTITY TOKEN")
|
||||||
|
assert.ErrorIs(t, identity.ErrVerifyIdentityTokenNotSupported, err)
|
||||||
|
assert.Nil(t, claims)
|
||||||
|
}
|
||||||
|
|
|
@ -360,3 +360,13 @@ func (p *Provider) SignOut(w http.ResponseWriter, r *http.Request, idTokenHint,
|
||||||
httputil.Redirect(w, r, endSessionURL.String(), http.StatusFound)
|
httputil.Redirect(w, r, endSessionURL.String(), http.StatusFound)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyAccessToken verifies an access token.
|
||||||
|
func (p *Provider) VerifyAccessToken(_ context.Context, _ string) (claims map[string]any, err error) {
|
||||||
|
return nil, identity.ErrVerifyAccessTokenNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyIdentityToken verifies an identity token.
|
||||||
|
func (p *Provider) VerifyIdentityToken(_ context.Context, _ string) (claims map[string]any, err error) {
|
||||||
|
return nil, identity.ErrVerifyIdentityTokenNotSupported
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
|
oteltrace "go.opentelemetry.io/otel/trace"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/pkg/identity/identity"
|
"github.com/pomerium/pomerium/pkg/identity/identity"
|
||||||
|
@ -23,7 +24,6 @@ import (
|
||||||
"github.com/pomerium/pomerium/pkg/identity/oidc/okta"
|
"github.com/pomerium/pomerium/pkg/identity/oidc/okta"
|
||||||
"github.com/pomerium/pomerium/pkg/identity/oidc/onelogin"
|
"github.com/pomerium/pomerium/pkg/identity/oidc/onelogin"
|
||||||
"github.com/pomerium/pomerium/pkg/identity/oidc/ping"
|
"github.com/pomerium/pomerium/pkg/identity/oidc/ping"
|
||||||
oteltrace "go.opentelemetry.io/otel/trace"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// State is the identity state.
|
// State is the identity state.
|
||||||
|
@ -36,6 +36,8 @@ type Authenticator interface {
|
||||||
Revoke(context.Context, *oauth2.Token) error
|
Revoke(context.Context, *oauth2.Token) error
|
||||||
Name() string
|
Name() string
|
||||||
UpdateUserInfo(ctx context.Context, t *oauth2.Token, v any) error
|
UpdateUserInfo(ctx context.Context, t *oauth2.Token, v any) error
|
||||||
|
VerifyAccessToken(ctx context.Context, rawAccessToken string) (claims map[string]any, err error)
|
||||||
|
VerifyIdentityToken(ctx context.Context, rawIdentityToken string) (claims map[string]any, err error)
|
||||||
|
|
||||||
SignIn(w http.ResponseWriter, r *http.Request, state string) error
|
SignIn(w http.ResponseWriter, r *http.Request, state string) error
|
||||||
SignOut(w http.ResponseWriter, r *http.Request, idTokenHint, authenticateSignedOutURL, redirectToURL string) error
|
SignOut(w http.ResponseWriter, r *http.Request, idTokenHint, authenticateSignedOutURL, redirectToURL string) error
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue