diff --git a/.github/workflows/test-docker-compose-linux.yml b/.github/workflows/test-docker-compose-linux.yml index 274edcb6b..3910de0f2 100644 --- a/.github/workflows/test-docker-compose-linux.yml +++ b/.github/workflows/test-docker-compose-linux.yml @@ -69,6 +69,7 @@ jobs: CI_NETBIRD_AUTH_JWT_CERTS: https://example.eu.auth0.com/.well-known/jwks.json CI_NETBIRD_AUTH_TOKEN_ENDPOINT: https://example.eu.auth0.com/oauth/token CI_NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT: https://example.eu.auth0.com/oauth/device/code + CI_NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT: https://example.eu.auth0.com/authorize CI_NETBIRD_AUTH_REDIRECT_URI: "/peers" CI_NETBIRD_TOKEN_SOURCE: "idToken" CI_NETBIRD_AUTH_USER_ID_CLAIM: "email" @@ -91,8 +92,8 @@ jobs: grep LETSENCRYPT_DOMAIN docker-compose.yml | egrep 'LETSENCRYPT_DOMAIN=$' grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE grep AuthUserIDClaim management.json | grep $CI_NETBIRD_AUTH_USER_ID_CLAIM - grep -A 1 ProviderConfig management.json | grep Audience | grep $CI_NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE - grep Scope management.json | grep "$CI_NETBIRD_AUTH_DEVICE_AUTH_SCOPE" + grep -A 3 DeviceAuthorizationFlow management.json | grep -A 1 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE + grep -A 8 DeviceAuthorizationFlow management.json | grep -A 6 ProviderConfig | grep Scope | grep "$CI_NETBIRD_AUTH_DEVICE_AUTH_SCOPE" grep UseIDToken management.json | grep false grep -A 1 IdpManagerConfig management.json | grep ManagerType | grep $CI_NETBIRD_MGMT_IDP grep -A 3 IdpManagerConfig management.json | grep -A 1 ClientConfig | grep Issuer | grep $CI_NETBIRD_AUTH_AUTHORITY @@ -100,6 +101,12 @@ jobs: grep -A 5 IdpManagerConfig management.json | grep -A 3 ClientConfig | grep ClientID | grep $CI_NETBIRD_IDP_MGMT_CLIENT_ID grep -A 6 IdpManagerConfig management.json | grep -A 4 ClientConfig | grep ClientSecret | grep $CI_NETBIRD_IDP_MGMT_CLIENT_SECRET grep -A 7 IdpManagerConfig management.json | grep -A 5 ClientConfig | grep GrantType | grep client_credentials + grep -A 2 PKCEAuthorizationFlow management.json | grep -A 1 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_AUDIENCE + grep -A 3 PKCEAuthorizationFlow management.json | grep -A 2 ProviderConfig | grep ClientID | grep $CI_NETBIRD_AUTH_CLIENT_ID + grep -A 4 PKCEAuthorizationFlow management.json | grep -A 3 ProviderConfig | grep ClientSecret | grep $CI_NETBIRD_AUTH_CLIENT_SECRET + grep -A 5 PKCEAuthorizationFlow management.json | grep -A 4 ProviderConfig | grep AuthorizationEndpoint | grep $CI_NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT + grep -A 6 PKCEAuthorizationFlow management.json | grep -A 5 ProviderConfig | grep TokenEndpoint | grep $CI_NETBIRD_AUTH_TOKEN_ENDPOINT + grep -A 7 PKCEAuthorizationFlow management.json | grep -A 6 ProviderConfig | grep Scope | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES" - name: run docker compose up working-directory: infrastructure_files diff --git a/client/android/login.go b/client/android/login.go index 518942cb6..ad334541c 100644 --- a/client/android/login.go +++ b/client/android/login.go @@ -6,15 +6,14 @@ import ( "time" "github.com/cenkalti/backoff/v4" - log "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" gstatus "google.golang.org/grpc/status" "github.com/netbirdio/netbird/client/cmd" - "github.com/netbirdio/netbird/client/system" - "github.com/netbirdio/netbird/client/internal" + "github.com/netbirdio/netbird/client/internal/auth" + "github.com/netbirdio/netbird/client/system" ) // SSOListener is async listener for mobile framework @@ -87,9 +86,15 @@ func (a *Auth) saveConfigIfSSOSupported() (bool, error) { err := a.withBackOff(a.ctx, func() (err error) { _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound { - supportsSSO = false - err = nil + _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) + if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound { + supportsSSO = false + err = nil + } + + return err } + return err }) @@ -183,27 +188,15 @@ func (a *Auth) login(urlOpener URLOpener) error { return nil } -func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*internal.TokenInfo, error) { - providerConfig, err := internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) +func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, error) { + oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config) if err != nil { - s, ok := gstatus.FromError(err) - if ok && s.Code() == codes.NotFound { - return nil, fmt.Errorf("no SSO provider returned from management. " + - "If you are using hosting Netbird see documentation at " + - "https://github.com/netbirdio/netbird/tree/main/management for details") - } else if ok && s.Code() == codes.Unimplemented { - return nil, fmt.Errorf("the management server, %s, does not support SSO providers, "+ - "please update your servver or use Setup Keys to login", a.config.ManagementURL) - } else { - return nil, fmt.Errorf("getting device authorization flow info failed with error: %v", err) - } + return nil, err } - hostedClient := internal.NewHostedDeviceFlow(providerConfig.ProviderConfig) - - flowInfo, err := hostedClient.RequestDeviceCode(context.TODO()) + flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO()) if err != nil { - return nil, fmt.Errorf("getting a request device code failed: %v", err) + return nil, fmt.Errorf("getting a request OAuth flow info failed: %v", err) } go urlOpener.Open(flowInfo.VerificationURIComplete) @@ -211,7 +204,7 @@ func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*internal.TokenInfo, waitTimeout := time.Duration(flowInfo.ExpiresIn) waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout*time.Second) defer cancel() - tokenInfo, err := hostedClient.WaitToken(waitCTX, flowInfo) + tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo) if err != nil { return nil, fmt.Errorf("waiting for browser login failed: %v", err) } diff --git a/client/cmd/login.go b/client/cmd/login.go index 83ed7f3b8..566f661a3 100644 --- a/client/cmd/login.go +++ b/client/cmd/login.go @@ -3,6 +3,7 @@ package cmd import ( "context" "fmt" + "github.com/netbirdio/netbird/client/internal/auth" "strings" "time" @@ -163,31 +164,15 @@ func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *internal.C return nil } -func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *internal.Config) (*internal.TokenInfo, error) { - providerConfig, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL) +func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *internal.Config) (*auth.TokenInfo, error) { + oAuthFlow, err := auth.NewOAuthFlow(ctx, config) if err != nil { - s, ok := gstatus.FromError(err) - if ok && s.Code() == codes.NotFound { - return nil, fmt.Errorf("no SSO provider returned from management. " + - "If you are using hosting Netbird see documentation at " + - "https://github.com/netbirdio/netbird/tree/main/management for details") - } else if ok && s.Code() == codes.Unimplemented { - mgmtURL := managementURL - if mgmtURL == "" { - mgmtURL = internal.DefaultManagementURL - } - return nil, fmt.Errorf("the management server, %s, does not support SSO providers, "+ - "please update your servver or use Setup Keys to login", mgmtURL) - } else { - return nil, fmt.Errorf("getting device authorization flow info failed with error: %v", err) - } + return nil, err } - hostedClient := internal.NewHostedDeviceFlow(providerConfig.ProviderConfig) - - flowInfo, err := hostedClient.RequestDeviceCode(context.TODO()) + flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO()) if err != nil { - return nil, fmt.Errorf("getting a request device code failed: %v", err) + return nil, fmt.Errorf("getting a request OAuth flow info failed: %v", err) } openURL(cmd, flowInfo.VerificationURIComplete, flowInfo.UserCode) @@ -196,7 +181,7 @@ func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *int waitCTX, c := context.WithTimeout(context.TODO(), waitTimeout*time.Second) defer c() - tokenInfo, err := hostedClient.WaitToken(waitCTX, flowInfo) + tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo) if err != nil { return nil, fmt.Errorf("waiting for browser login failed: %v", err) } @@ -206,8 +191,10 @@ func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *int func openURL(cmd *cobra.Command, verificationURIComplete, userCode string) { var codeMsg string - if !strings.Contains(verificationURIComplete, userCode) { - codeMsg = fmt.Sprintf("and enter the code %s to authenticate.", userCode) + if userCode != "" { + if !strings.Contains(verificationURIComplete, userCode) { + codeMsg = fmt.Sprintf("and enter the code %s to authenticate.", userCode) + } } err := open.Run(verificationURIComplete) diff --git a/client/internal/auth/device_flow.go b/client/internal/auth/device_flow.go new file mode 100644 index 000000000..c28e42772 --- /dev/null +++ b/client/internal/auth/device_flow.go @@ -0,0 +1,202 @@ +package auth + +import ( + "context" + "encoding/json" + "fmt" + "github.com/netbirdio/netbird/client/internal" + "io" + "net/http" + "net/url" + "strings" + "time" +) + +// HostedGrantType grant type for device flow on Hosted +const ( + HostedGrantType = "urn:ietf:params:oauth:grant-type:device_code" +) + +var _ OAuthFlow = &DeviceAuthorizationFlow{} + +// DeviceAuthorizationFlow implements the OAuthFlow interface, +// for the Device Authorization Flow. +type DeviceAuthorizationFlow struct { + providerConfig internal.DeviceAuthProviderConfig + + HTTPClient HTTPClient +} + +// RequestDeviceCodePayload used for request device code payload for auth0 +type RequestDeviceCodePayload struct { + Audience string `json:"audience"` + ClientID string `json:"client_id"` + Scope string `json:"scope"` +} + +// TokenRequestPayload used for requesting the auth0 token +type TokenRequestPayload struct { + GrantType string `json:"grant_type"` + DeviceCode string `json:"device_code,omitempty"` + ClientID string `json:"client_id"` + RefreshToken string `json:"refresh_token,omitempty"` +} + +// TokenRequestResponse used for parsing Hosted token's response +type TokenRequestResponse struct { + Error string `json:"error"` + ErrorDescription string `json:"error_description"` + TokenInfo +} + +// NewDeviceAuthorizationFlow returns device authorization flow client +func NewDeviceAuthorizationFlow(config internal.DeviceAuthProviderConfig) (*DeviceAuthorizationFlow, error) { + httpTransport := http.DefaultTransport.(*http.Transport).Clone() + httpTransport.MaxIdleConns = 5 + + httpClient := &http.Client{ + Timeout: 10 * time.Second, + Transport: httpTransport, + } + + return &DeviceAuthorizationFlow{ + providerConfig: config, + HTTPClient: httpClient, + }, nil +} + +// GetClientID returns the provider client id +func (d *DeviceAuthorizationFlow) GetClientID(ctx context.Context) string { + return d.providerConfig.ClientID +} + +// RequestAuthInfo requests a device code login flow information from Hosted +func (d *DeviceAuthorizationFlow) RequestAuthInfo(ctx context.Context) (AuthFlowInfo, error) { + form := url.Values{} + form.Add("client_id", d.providerConfig.ClientID) + form.Add("audience", d.providerConfig.Audience) + form.Add("scope", d.providerConfig.Scope) + req, err := http.NewRequest("POST", d.providerConfig.DeviceAuthEndpoint, + strings.NewReader(form.Encode())) + if err != nil { + return AuthFlowInfo{}, fmt.Errorf("creating request failed with error: %v", err) + } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + res, err := d.HTTPClient.Do(req) + if err != nil { + return AuthFlowInfo{}, fmt.Errorf("doing request failed with error: %v", err) + } + + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return AuthFlowInfo{}, fmt.Errorf("reading body failed with error: %v", err) + } + + if res.StatusCode != 200 { + return AuthFlowInfo{}, fmt.Errorf("request device code returned status %d error: %s", res.StatusCode, string(body)) + } + + deviceCode := AuthFlowInfo{} + err = json.Unmarshal(body, &deviceCode) + if err != nil { + return AuthFlowInfo{}, fmt.Errorf("unmarshaling response failed with error: %v", err) + } + + // Fallback to the verification_uri if the IdP doesn't support verification_uri_complete + if deviceCode.VerificationURIComplete == "" { + deviceCode.VerificationURIComplete = deviceCode.VerificationURI + } + + return deviceCode, err +} + +func (d *DeviceAuthorizationFlow) requestToken(info AuthFlowInfo) (TokenRequestResponse, error) { + form := url.Values{} + form.Add("client_id", d.providerConfig.ClientID) + form.Add("grant_type", HostedGrantType) + form.Add("device_code", info.DeviceCode) + + req, err := http.NewRequest("POST", d.providerConfig.TokenEndpoint, strings.NewReader(form.Encode())) + if err != nil { + return TokenRequestResponse{}, fmt.Errorf("failed to create request access token: %v", err) + } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + res, err := d.HTTPClient.Do(req) + if err != nil { + return TokenRequestResponse{}, fmt.Errorf("failed to request access token with error: %v", err) + } + + defer func() { + err := res.Body.Close() + if err != nil { + return + } + }() + + body, err := io.ReadAll(res.Body) + if err != nil { + return TokenRequestResponse{}, fmt.Errorf("failed reading access token response body with error: %v", err) + } + + if res.StatusCode > 499 { + return TokenRequestResponse{}, fmt.Errorf("access token response returned code: %s", string(body)) + } + + tokenResponse := TokenRequestResponse{} + err = json.Unmarshal(body, &tokenResponse) + if err != nil { + return TokenRequestResponse{}, fmt.Errorf("parsing token response failed with error: %v", err) + } + + return tokenResponse, nil +} + +// WaitToken waits user's login and authorize the app. Once the user's authorize +// it retrieves the access token from Hosted's endpoint and validates it before returning +func (d *DeviceAuthorizationFlow) WaitToken(ctx context.Context, info AuthFlowInfo) (TokenInfo, error) { + interval := time.Duration(info.Interval) * time.Second + ticker := time.NewTicker(interval) + for { + select { + case <-ctx.Done(): + return TokenInfo{}, ctx.Err() + case <-ticker.C: + + tokenResponse, err := d.requestToken(info) + if err != nil { + return TokenInfo{}, fmt.Errorf("parsing token response failed with error: %v", err) + } + + if tokenResponse.Error != "" { + if tokenResponse.Error == "authorization_pending" { + continue + } else if tokenResponse.Error == "slow_down" { + interval = interval + (3 * time.Second) + ticker.Reset(interval) + continue + } + + return TokenInfo{}, fmt.Errorf(tokenResponse.ErrorDescription) + } + + tokenInfo := TokenInfo{ + AccessToken: tokenResponse.AccessToken, + TokenType: tokenResponse.TokenType, + RefreshToken: tokenResponse.RefreshToken, + IDToken: tokenResponse.IDToken, + ExpiresIn: tokenResponse.ExpiresIn, + UseIDToken: d.providerConfig.UseIDToken, + } + + err = isValidAccessToken(tokenInfo.GetTokenToUse(), d.providerConfig.Audience) + if err != nil { + return TokenInfo{}, fmt.Errorf("validate access token failed with error: %v", err) + } + + return tokenInfo, err + } + } +} diff --git a/client/internal/oauth_test.go b/client/internal/auth/device_flow_test.go similarity index 93% rename from client/internal/oauth_test.go rename to client/internal/auth/device_flow_test.go index aa71fa0eb..dc950ac63 100644 --- a/client/internal/oauth_test.go +++ b/client/internal/auth/device_flow_test.go @@ -1,17 +1,17 @@ -package internal +package auth import ( "context" "fmt" + "github.com/golang-jwt/jwt" + "github.com/netbirdio/netbird/client/internal" + "github.com/stretchr/testify/require" "io" "net/http" "net/url" "strings" "testing" "time" - - "github.com/golang-jwt/jwt" - "github.com/stretchr/testify/require" ) type mockHTTPClient struct { @@ -53,7 +53,7 @@ func TestHosted_RequestDeviceCode(t *testing.T) { testingErrFunc require.ErrorAssertionFunc expectedErrorMSG string testingFunc require.ComparisonAssertionFunc - expectedOut DeviceAuthInfo + expectedOut AuthFlowInfo expectedMSG string expectPayload string } @@ -92,7 +92,7 @@ func TestHosted_RequestDeviceCode(t *testing.T) { testingFunc: require.EqualValues, expectPayload: expectPayload, } - testCase4Out := DeviceAuthInfo{ExpiresIn: 10} + testCase4Out := AuthFlowInfo{ExpiresIn: 10} testCase4 := test{ name: "Got Device Code", inputResBody: fmt.Sprintf("{\"expires_in\":%d}", testCase4Out.ExpiresIn), @@ -113,8 +113,8 @@ func TestHosted_RequestDeviceCode(t *testing.T) { err: testCase.inputReqError, } - hosted := Hosted{ - providerConfig: ProviderConfig{ + deviceFlow := &DeviceAuthorizationFlow{ + providerConfig: internal.DeviceAuthProviderConfig{ Audience: expectedAudience, ClientID: expectedClientID, Scope: expectedScope, @@ -125,7 +125,7 @@ func TestHosted_RequestDeviceCode(t *testing.T) { HTTPClient: &httpClient, } - authInfo, err := hosted.RequestDeviceCode(context.TODO()) + authInfo, err := deviceFlow.RequestAuthInfo(context.TODO()) testCase.testingErrFunc(t, err, testCase.expectedErrorMSG) require.EqualValues(t, expectPayload, httpClient.reqBody, "payload should match") @@ -145,7 +145,7 @@ func TestHosted_WaitToken(t *testing.T) { inputMaxReqs int inputCountResBody string inputTimeout time.Duration - inputInfo DeviceAuthInfo + inputInfo AuthFlowInfo inputAudience string testingErrFunc require.ErrorAssertionFunc expectedErrorMSG string @@ -155,7 +155,7 @@ func TestHosted_WaitToken(t *testing.T) { expectPayload string } - defaultInfo := DeviceAuthInfo{ + defaultInfo := AuthFlowInfo{ DeviceCode: "test", ExpiresIn: 10, Interval: 1, @@ -278,8 +278,8 @@ func TestHosted_WaitToken(t *testing.T) { countResBody: testCase.inputCountResBody, } - hosted := Hosted{ - providerConfig: ProviderConfig{ + deviceFlow := DeviceAuthorizationFlow{ + providerConfig: internal.DeviceAuthProviderConfig{ Audience: testCase.inputAudience, ClientID: clientID, TokenEndpoint: "test.hosted.com/token", @@ -287,11 +287,12 @@ func TestHosted_WaitToken(t *testing.T) { Scope: "openid", UseIDToken: false, }, - HTTPClient: &httpClient} + HTTPClient: &httpClient, + } ctx, cancel := context.WithTimeout(context.TODO(), testCase.inputTimeout) defer cancel() - tokenInfo, err := hosted.WaitToken(ctx, testCase.inputInfo) + tokenInfo, err := deviceFlow.WaitToken(ctx, testCase.inputInfo) testCase.testingErrFunc(t, err, testCase.expectedErrorMSG) require.EqualValues(t, testCase.expectPayload, httpClient.reqBody, "payload should match") diff --git a/client/internal/auth/oauth.go b/client/internal/auth/oauth.go new file mode 100644 index 000000000..d7365df60 --- /dev/null +++ b/client/internal/auth/oauth.go @@ -0,0 +1,90 @@ +package auth + +import ( + "context" + "fmt" + "net/http" + + log "github.com/sirupsen/logrus" + "google.golang.org/grpc/codes" + gstatus "google.golang.org/grpc/status" + + "github.com/netbirdio/netbird/client/internal" +) + +// OAuthFlow represents an interface for authorization using different OAuth 2.0 flows +type OAuthFlow interface { + RequestAuthInfo(ctx context.Context) (AuthFlowInfo, error) + WaitToken(ctx context.Context, info AuthFlowInfo) (TokenInfo, error) + GetClientID(ctx context.Context) string +} + +// HTTPClient http client interface for API calls +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +// AuthFlowInfo holds information for the OAuth 2.0 authorization flow +type AuthFlowInfo struct { + DeviceCode string `json:"device_code"` + UserCode string `json:"user_code"` + VerificationURI string `json:"verification_uri"` + VerificationURIComplete string `json:"verification_uri_complete"` + ExpiresIn int `json:"expires_in"` + Interval int `json:"interval"` +} + +// Claims used when validating the access token +type Claims struct { + Audience interface{} `json:"aud"` +} + +// TokenInfo holds information of issued access token +type TokenInfo struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + IDToken string `json:"id_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + UseIDToken bool `json:"-"` +} + +// GetTokenToUse returns either the access or id token based on UseIDToken field +func (t TokenInfo) GetTokenToUse() string { + if t.UseIDToken { + return t.IDToken + } + return t.AccessToken +} + +// NewOAuthFlow initializes and returns the appropriate OAuth flow based on the management configuration. +func NewOAuthFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) { + log.Debug("getting device authorization flow info") + + // Try to initialize the Device Authorization Flow + deviceFlowInfo, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL) + if err == nil { + return NewDeviceAuthorizationFlow(deviceFlowInfo.ProviderConfig) + } + + log.Debugf("getting device authorization flow info failed with error: %v", err) + log.Debugf("falling back to pkce authorization flow info") + + // If Device Authorization Flow failed, try the PKCE Authorization Flow + pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL) + if err != nil { + s, ok := gstatus.FromError(err) + if ok && s.Code() == codes.NotFound { + return nil, fmt.Errorf("no SSO provider returned from management. " + + "If you are using hosting Netbird see documentation at " + + "https://github.com/netbirdio/netbird/tree/main/management for details") + } else if ok && s.Code() == codes.Unimplemented { + return nil, fmt.Errorf("the management server, %s, does not support SSO providers, "+ + "please update your server or use Setup Keys to login", config.ManagementURL) + } else { + return nil, fmt.Errorf("getting pkce authorization flow info failed with error: %v", err) + } + } + + return NewPKCEAuthorizationFlow(pkceFlowInfo.ProviderConfig) +} diff --git a/client/internal/auth/pkce_flow.go b/client/internal/auth/pkce_flow.go new file mode 100644 index 000000000..9451ce055 --- /dev/null +++ b/client/internal/auth/pkce_flow.go @@ -0,0 +1,217 @@ +package auth + +import ( + "context" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "fmt" + "net" + "net/http" + "net/url" + "strings" + "time" + + log "github.com/sirupsen/logrus" + "golang.org/x/oauth2" + + "github.com/netbirdio/netbird/client/internal" +) + +var _ OAuthFlow = &PKCEAuthorizationFlow{} + +const ( + queryState = "state" + queryCode = "code" + defaultPKCETimeoutSeconds = 300 +) + +// PKCEAuthorizationFlow implements the OAuthFlow interface for +// the Authorization Code Flow with PKCE. +type PKCEAuthorizationFlow struct { + providerConfig internal.PKCEAuthProviderConfig + state string + codeVerifier string + oAuthConfig *oauth2.Config +} + +// NewPKCEAuthorizationFlow returns new PKCE authorization code flow. +func NewPKCEAuthorizationFlow(config internal.PKCEAuthProviderConfig) (*PKCEAuthorizationFlow, error) { + var availableRedirectURL string + + // find the first available redirect URL + for _, redirectURL := range config.RedirectURLs { + if !isRedirectURLPortUsed(redirectURL) { + availableRedirectURL = redirectURL + break + } + } + + if availableRedirectURL == "" { + return nil, fmt.Errorf("no available port found from configured redirect URLs: %q", config.RedirectURLs) + } + + cfg := &oauth2.Config{ + ClientID: config.ClientID, + ClientSecret: config.ClientSecret, + Endpoint: oauth2.Endpoint{ + AuthURL: config.AuthorizationEndpoint, + TokenURL: config.TokenEndpoint, + }, + RedirectURL: availableRedirectURL, + Scopes: strings.Split(config.Scope, " "), + } + + return &PKCEAuthorizationFlow{ + providerConfig: config, + oAuthConfig: cfg, + }, nil +} + +// GetClientID returns the provider client id +func (p *PKCEAuthorizationFlow) GetClientID(_ context.Context) string { + return p.providerConfig.ClientID +} + +// RequestAuthInfo requests a authorization code login flow information. +func (p *PKCEAuthorizationFlow) RequestAuthInfo(_ context.Context) (AuthFlowInfo, error) { + state, err := randomBytesInHex(24) + if err != nil { + return AuthFlowInfo{}, fmt.Errorf("could not generate random state: %v", err) + } + p.state = state + + codeVerifier, err := randomBytesInHex(64) + if err != nil { + return AuthFlowInfo{}, fmt.Errorf("could not create a code verifier: %v", err) + } + p.codeVerifier = codeVerifier + + codeChallenge := createCodeChallenge(codeVerifier) + authURL := p.oAuthConfig.AuthCodeURL( + state, + oauth2.SetAuthURLParam("code_challenge_method", "S256"), + oauth2.SetAuthURLParam("code_challenge", codeChallenge), + oauth2.SetAuthURLParam("audience", p.providerConfig.Audience), + ) + + return AuthFlowInfo{ + VerificationURIComplete: authURL, + ExpiresIn: defaultPKCETimeoutSeconds, + }, nil +} + +// WaitToken waits for the OAuth token in the PKCE Authorization Flow. +// It starts an HTTP server to receive the OAuth token callback and waits for the token or an error. +// Once the token is received, it is converted to TokenInfo and validated before returning. +func (p *PKCEAuthorizationFlow) WaitToken(ctx context.Context, _ AuthFlowInfo) (TokenInfo, error) { + tokenChan := make(chan *oauth2.Token, 1) + errChan := make(chan error, 1) + + go p.startServer(tokenChan, errChan) + + select { + case <-ctx.Done(): + return TokenInfo{}, ctx.Err() + case token := <-tokenChan: + return p.handleOAuthToken(token) + case err := <-errChan: + return TokenInfo{}, err + } +} + +func (p *PKCEAuthorizationFlow) startServer(tokenChan chan<- *oauth2.Token, errChan chan<- error) { + parsedURL, err := url.Parse(p.oAuthConfig.RedirectURL) + if err != nil { + errChan <- fmt.Errorf("failed to parse redirect URL: %v", err) + return + } + port := parsedURL.Port() + + server := http.Server{Addr: fmt.Sprintf(":%s", port)} + defer func() { + if err := server.Shutdown(context.Background()); err != nil { + log.Errorf("error while shutting down pkce flow server: %v", err) + } + }() + + http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + query := req.URL.Query() + + state := query.Get(queryState) + // Prevent timing attacks on state + if subtle.ConstantTimeCompare([]byte(p.state), []byte(state)) == 0 { + errChan <- fmt.Errorf("invalid state") + return + } + + code := query.Get(queryCode) + if code == "" { + errChan <- fmt.Errorf("missing code") + return + } + + // Exchange the authorization code for the OAuth token + token, err := p.oAuthConfig.Exchange( + req.Context(), + code, + oauth2.SetAuthURLParam("code_verifier", p.codeVerifier), + ) + if err != nil { + errChan <- fmt.Errorf("OAuth token exchange failed: %v", err) + return + } + + tokenChan <- token + }) + + if err := server.ListenAndServe(); err != nil { + errChan <- err + } +} + +func (p *PKCEAuthorizationFlow) handleOAuthToken(token *oauth2.Token) (TokenInfo, error) { + tokenInfo := TokenInfo{ + AccessToken: token.AccessToken, + RefreshToken: token.RefreshToken, + TokenType: token.TokenType, + ExpiresIn: token.Expiry.Second(), + UseIDToken: p.providerConfig.UseIDToken, + } + if idToken, ok := token.Extra("id_token").(string); ok { + tokenInfo.IDToken = idToken + } + + if err := isValidAccessToken(tokenInfo.GetTokenToUse(), p.providerConfig.Audience); err != nil { + return TokenInfo{}, fmt.Errorf("validate access token failed with error: %v", err) + } + + return tokenInfo, nil +} + +func createCodeChallenge(codeVerifier string) string { + sha2 := sha256.Sum256([]byte(codeVerifier)) + return base64.RawURLEncoding.EncodeToString(sha2[:]) +} + +// isRedirectURLPortUsed checks if the port used in the redirect URL is in use. +func isRedirectURLPortUsed(redirectURL string) bool { + parsedURL, err := url.Parse(redirectURL) + if err != nil { + log.Errorf("failed to parse redirect URL: %v", err) + return true + } + + addr := fmt.Sprintf(":%s", parsedURL.Port()) + conn, err := net.DialTimeout("tcp", addr, 3*time.Second) + if err != nil { + return false + } + defer func() { + if err := conn.Close(); err != nil { + log.Errorf("error while closing the connection: %v", err) + } + }() + + return true +} diff --git a/client/internal/auth/util.go b/client/internal/auth/util.go new file mode 100644 index 000000000..33a0e6e35 --- /dev/null +++ b/client/internal/auth/util.go @@ -0,0 +1,62 @@ +package auth + +import ( + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "reflect" + "strings" +) + +func randomBytesInHex(count int) (string, error) { + buf := make([]byte, count) + _, err := io.ReadFull(rand.Reader, buf) + if err != nil { + return "", fmt.Errorf("could not generate %d random bytes: %v", count, err) + } + + return hex.EncodeToString(buf), nil +} + +// isValidAccessToken is a simple validation of the access token +func isValidAccessToken(token string, audience string) error { + if token == "" { + return fmt.Errorf("token received is empty") + } + + encodedClaims := strings.Split(token, ".")[1] + claimsString, err := base64.RawURLEncoding.DecodeString(encodedClaims) + if err != nil { + return err + } + + claims := Claims{} + err = json.Unmarshal(claimsString, &claims) + if err != nil { + return err + } + + if claims.Audience == nil { + return fmt.Errorf("required token field audience is absent") + } + + // Audience claim of JWT can be a string or an array of strings + typ := reflect.TypeOf(claims.Audience) + switch typ.Kind() { + case reflect.String: + if claims.Audience == audience { + return nil + } + case reflect.Slice: + for _, aud := range claims.Audience.([]interface{}) { + if audience == aud { + return nil + } + } + } + + return fmt.Errorf("invalid JWT token audience field") +} diff --git a/client/internal/device_auth.go b/client/internal/device_auth.go index 0273bb8e4..8e68f7544 100644 --- a/client/internal/device_auth.go +++ b/client/internal/device_auth.go @@ -16,11 +16,11 @@ import ( // DeviceAuthorizationFlow represents Device Authorization Flow information type DeviceAuthorizationFlow struct { Provider string - ProviderConfig ProviderConfig + ProviderConfig DeviceAuthProviderConfig } -// ProviderConfig has all attributes needed to initiate a device authorization flow -type ProviderConfig struct { +// DeviceAuthProviderConfig has all attributes needed to initiate a device authorization flow +type DeviceAuthProviderConfig struct { // ClientID An IDP application client id ClientID string // ClientSecret An IDP application client secret @@ -88,7 +88,7 @@ func GetDeviceAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmU deviceAuthorizationFlow := DeviceAuthorizationFlow{ Provider: protoDeviceAuthorizationFlow.Provider.String(), - ProviderConfig: ProviderConfig{ + ProviderConfig: DeviceAuthProviderConfig{ Audience: protoDeviceAuthorizationFlow.GetProviderConfig().GetAudience(), ClientID: protoDeviceAuthorizationFlow.GetProviderConfig().GetClientID(), ClientSecret: protoDeviceAuthorizationFlow.GetProviderConfig().GetClientSecret(), @@ -105,7 +105,7 @@ func GetDeviceAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmU deviceAuthorizationFlow.ProviderConfig.Scope = "openid" } - err = isProviderConfigValid(deviceAuthorizationFlow.ProviderConfig) + err = isDeviceAuthProviderConfigValid(deviceAuthorizationFlow.ProviderConfig) if err != nil { return DeviceAuthorizationFlow{}, err } @@ -113,7 +113,7 @@ func GetDeviceAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmU return deviceAuthorizationFlow, nil } -func isProviderConfigValid(config ProviderConfig) error { +func isDeviceAuthProviderConfigValid(config DeviceAuthProviderConfig) error { errorMSGFormat := "invalid provider configuration received from management: %s value is empty. Contact your NetBird administrator" if config.Audience == "" { return fmt.Errorf(errorMSGFormat, "Audience") diff --git a/client/internal/oauth.go b/client/internal/oauth.go deleted file mode 100644 index f8b8b8adb..000000000 --- a/client/internal/oauth.go +++ /dev/null @@ -1,286 +0,0 @@ -package internal - -import ( - "context" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "reflect" - "strings" - "time" -) - -// OAuthClient is a OAuth client interface for various idp providers -type OAuthClient interface { - RequestDeviceCode(ctx context.Context) (DeviceAuthInfo, error) - WaitToken(ctx context.Context, info DeviceAuthInfo) (TokenInfo, error) - GetClientID(ctx context.Context) string -} - -// HTTPClient http client interface for API calls -type HTTPClient interface { - Do(req *http.Request) (*http.Response, error) -} - -// DeviceAuthInfo holds information for the OAuth device login flow -type DeviceAuthInfo struct { - DeviceCode string `json:"device_code"` - UserCode string `json:"user_code"` - VerificationURI string `json:"verification_uri"` - VerificationURIComplete string `json:"verification_uri_complete"` - ExpiresIn int `json:"expires_in"` - Interval int `json:"interval"` -} - -// HostedGrantType grant type for device flow on Hosted -const ( - HostedGrantType = "urn:ietf:params:oauth:grant-type:device_code" - HostedRefreshGrant = "refresh_token" -) - -// Hosted client -type Hosted struct { - providerConfig ProviderConfig - - HTTPClient HTTPClient -} - -// RequestDeviceCodePayload used for request device code payload for auth0 -type RequestDeviceCodePayload struct { - Audience string `json:"audience"` - ClientID string `json:"client_id"` - Scope string `json:"scope"` -} - -// TokenRequestPayload used for requesting the auth0 token -type TokenRequestPayload struct { - GrantType string `json:"grant_type"` - DeviceCode string `json:"device_code,omitempty"` - ClientID string `json:"client_id"` - RefreshToken string `json:"refresh_token,omitempty"` -} - -// TokenRequestResponse used for parsing Hosted token's response -type TokenRequestResponse struct { - Error string `json:"error"` - ErrorDescription string `json:"error_description"` - TokenInfo -} - -// Claims used when validating the access token -type Claims struct { - Audience interface{} `json:"aud"` -} - -// TokenInfo holds information of issued access token -type TokenInfo struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - IDToken string `json:"id_token"` - TokenType string `json:"token_type"` - ExpiresIn int `json:"expires_in"` - UseIDToken bool `json:"-"` -} - -// GetTokenToUse returns either the access or id token based on UseIDToken field -func (t TokenInfo) GetTokenToUse() string { - if t.UseIDToken { - return t.IDToken - } - return t.AccessToken -} - -// NewHostedDeviceFlow returns an Hosted OAuth client -func NewHostedDeviceFlow(config ProviderConfig) *Hosted { - httpTransport := http.DefaultTransport.(*http.Transport).Clone() - httpTransport.MaxIdleConns = 5 - - httpClient := &http.Client{ - Timeout: 10 * time.Second, - Transport: httpTransport, - } - - return &Hosted{ - providerConfig: config, - HTTPClient: httpClient, - } -} - -// GetClientID returns the provider client id -func (h *Hosted) GetClientID(ctx context.Context) string { - return h.providerConfig.ClientID -} - -// RequestDeviceCode requests a device code login flow information from Hosted -func (h *Hosted) RequestDeviceCode(ctx context.Context) (DeviceAuthInfo, error) { - form := url.Values{} - form.Add("client_id", h.providerConfig.ClientID) - form.Add("audience", h.providerConfig.Audience) - form.Add("scope", h.providerConfig.Scope) - req, err := http.NewRequest("POST", h.providerConfig.DeviceAuthEndpoint, - strings.NewReader(form.Encode())) - if err != nil { - return DeviceAuthInfo{}, fmt.Errorf("creating request failed with error: %v", err) - } - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - res, err := h.HTTPClient.Do(req) - if err != nil { - return DeviceAuthInfo{}, fmt.Errorf("doing request failed with error: %v", err) - } - - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - return DeviceAuthInfo{}, fmt.Errorf("reading body failed with error: %v", err) - } - - if res.StatusCode != 200 { - return DeviceAuthInfo{}, fmt.Errorf("request device code returned status %d error: %s", res.StatusCode, string(body)) - } - - deviceCode := DeviceAuthInfo{} - err = json.Unmarshal(body, &deviceCode) - if err != nil { - return DeviceAuthInfo{}, fmt.Errorf("unmarshaling response failed with error: %v", err) - } - - // Fallback to the verification_uri if the IdP doesn't support verification_uri_complete - if deviceCode.VerificationURIComplete == "" { - deviceCode.VerificationURIComplete = deviceCode.VerificationURI - } - - return deviceCode, err -} - -func (h *Hosted) requestToken(info DeviceAuthInfo) (TokenRequestResponse, error) { - form := url.Values{} - form.Add("client_id", h.providerConfig.ClientID) - form.Add("grant_type", HostedGrantType) - form.Add("device_code", info.DeviceCode) - req, err := http.NewRequest("POST", h.providerConfig.TokenEndpoint, strings.NewReader(form.Encode())) - if err != nil { - return TokenRequestResponse{}, fmt.Errorf("failed to create request access token: %v", err) - } - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - res, err := h.HTTPClient.Do(req) - if err != nil { - return TokenRequestResponse{}, fmt.Errorf("failed to request access token with error: %v", err) - } - - defer func() { - err := res.Body.Close() - if err != nil { - return - } - }() - - body, err := io.ReadAll(res.Body) - if err != nil { - return TokenRequestResponse{}, fmt.Errorf("failed reading access token response body with error: %v", err) - } - - if res.StatusCode > 499 { - return TokenRequestResponse{}, fmt.Errorf("access token response returned code: %s", string(body)) - } - - tokenResponse := TokenRequestResponse{} - err = json.Unmarshal(body, &tokenResponse) - if err != nil { - return TokenRequestResponse{}, fmt.Errorf("parsing token response failed with error: %v", err) - } - - return tokenResponse, nil -} - -// WaitToken waits user's login and authorize the app. Once the user's authorize -// it retrieves the access token from Hosted's endpoint and validates it before returning -func (h *Hosted) WaitToken(ctx context.Context, info DeviceAuthInfo) (TokenInfo, error) { - interval := time.Duration(info.Interval) * time.Second - ticker := time.NewTicker(interval) - for { - select { - case <-ctx.Done(): - return TokenInfo{}, ctx.Err() - case <-ticker.C: - - tokenResponse, err := h.requestToken(info) - if err != nil { - return TokenInfo{}, fmt.Errorf("parsing token response failed with error: %v", err) - } - - if tokenResponse.Error != "" { - if tokenResponse.Error == "authorization_pending" { - continue - } else if tokenResponse.Error == "slow_down" { - interval = interval + (3 * time.Second) - ticker.Reset(interval) - continue - } - - return TokenInfo{}, fmt.Errorf(tokenResponse.ErrorDescription) - } - - tokenInfo := TokenInfo{ - AccessToken: tokenResponse.AccessToken, - TokenType: tokenResponse.TokenType, - RefreshToken: tokenResponse.RefreshToken, - IDToken: tokenResponse.IDToken, - ExpiresIn: tokenResponse.ExpiresIn, - UseIDToken: h.providerConfig.UseIDToken, - } - - err = isValidAccessToken(tokenInfo.GetTokenToUse(), h.providerConfig.Audience) - if err != nil { - return TokenInfo{}, fmt.Errorf("validate access token failed with error: %v", err) - } - - return tokenInfo, err - } - } -} - -// isValidAccessToken is a simple validation of the access token -func isValidAccessToken(token string, audience string) error { - if token == "" { - return fmt.Errorf("token received is empty") - } - - encodedClaims := strings.Split(token, ".")[1] - claimsString, err := base64.RawURLEncoding.DecodeString(encodedClaims) - if err != nil { - return err - } - - claims := Claims{} - err = json.Unmarshal(claimsString, &claims) - if err != nil { - return err - } - - if claims.Audience == nil { - return fmt.Errorf("required token field audience is absent") - } - - // Audience claim of JWT can be a string or an array of strings - typ := reflect.TypeOf(claims.Audience) - switch typ.Kind() { - case reflect.String: - if claims.Audience == audience { - return nil - } - case reflect.Slice: - for _, aud := range claims.Audience.([]interface{}) { - if audience == aud { - return nil - } - } - } - - return fmt.Errorf("invalid JWT token audience field") -} diff --git a/client/internal/pkce_auth.go b/client/internal/pkce_auth.go new file mode 100644 index 000000000..2efbae97b --- /dev/null +++ b/client/internal/pkce_auth.go @@ -0,0 +1,128 @@ +package internal + +import ( + "context" + "fmt" + "net/url" + + log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + mgm "github.com/netbirdio/netbird/management/client" +) + +// PKCEAuthorizationFlow represents PKCE Authorization Flow information +type PKCEAuthorizationFlow struct { + ProviderConfig PKCEAuthProviderConfig +} + +// PKCEAuthProviderConfig has all attributes needed to initiate pkce authorization flow +type PKCEAuthProviderConfig struct { + // ClientID An IDP application client id + ClientID string + // ClientSecret An IDP application client secret + ClientSecret string + // Audience An Audience for to authorization validation + Audience string + // TokenEndpoint is the endpoint of an IDP manager where clients can obtain access token + TokenEndpoint string + // AuthorizationEndpoint is the endpoint of an IDP manager where clients can obtain authorization code + AuthorizationEndpoint string + // Scopes provides the scopes to be included in the token request + Scope string + // RedirectURL handles authorization code from IDP manager + RedirectURLs []string + // UseIDToken indicates if the id token should be used for authentication + UseIDToken bool +} + +// GetPKCEAuthorizationFlowInfo initialize a PKCEAuthorizationFlow instance and return with it +func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL *url.URL) (PKCEAuthorizationFlow, error) { + // validate our peer's Wireguard PRIVATE key + myPrivateKey, err := wgtypes.ParseKey(privateKey) + if err != nil { + log.Errorf("failed parsing Wireguard key %s: [%s]", privateKey, err.Error()) + return PKCEAuthorizationFlow{}, err + } + + var mgmTLSEnabled bool + if mgmURL.Scheme == "https" { + mgmTLSEnabled = true + } + + log.Debugf("connecting to Management Service %s", mgmURL.String()) + mgmClient, err := mgm.NewClient(ctx, mgmURL.Host, myPrivateKey, mgmTLSEnabled) + if err != nil { + log.Errorf("failed connecting to Management Service %s %v", mgmURL.String(), err) + return PKCEAuthorizationFlow{}, err + } + log.Debugf("connected to the Management service %s", mgmURL.String()) + + defer func() { + err = mgmClient.Close() + if err != nil { + log.Warnf("failed to close the Management service client %v", err) + } + }() + + serverKey, err := mgmClient.GetServerPublicKey() + if err != nil { + log.Errorf("failed while getting Management Service public key: %v", err) + return PKCEAuthorizationFlow{}, err + } + + protoPKCEAuthorizationFlow, err := mgmClient.GetPKCEAuthorizationFlow(*serverKey) + if err != nil { + if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { + log.Warnf("server couldn't find pkce flow, contact admin: %v", err) + return PKCEAuthorizationFlow{}, err + } + log.Errorf("failed to retrieve pkce flow: %v", err) + return PKCEAuthorizationFlow{}, err + } + + authFlow := PKCEAuthorizationFlow{ + ProviderConfig: PKCEAuthProviderConfig{ + Audience: protoPKCEAuthorizationFlow.GetProviderConfig().GetAudience(), + ClientID: protoPKCEAuthorizationFlow.GetProviderConfig().GetClientID(), + ClientSecret: protoPKCEAuthorizationFlow.GetProviderConfig().GetClientSecret(), + TokenEndpoint: protoPKCEAuthorizationFlow.GetProviderConfig().GetTokenEndpoint(), + AuthorizationEndpoint: protoPKCEAuthorizationFlow.GetProviderConfig().GetAuthorizationEndpoint(), + Scope: protoPKCEAuthorizationFlow.GetProviderConfig().GetScope(), + RedirectURLs: protoPKCEAuthorizationFlow.GetProviderConfig().GetRedirectURLs(), + UseIDToken: protoPKCEAuthorizationFlow.GetProviderConfig().GetUseIDToken(), + }, + } + + err = isPKCEProviderConfigValid(authFlow.ProviderConfig) + if err != nil { + return PKCEAuthorizationFlow{}, err + } + + return authFlow, nil +} + +func isPKCEProviderConfigValid(config PKCEAuthProviderConfig) error { + errorMSGFormat := "invalid provider configuration received from management: %s value is empty. Contact your NetBird administrator" + if config.Audience == "" { + return fmt.Errorf(errorMSGFormat, "Audience") + } + if config.ClientID == "" { + return fmt.Errorf(errorMSGFormat, "Client ID") + } + if config.TokenEndpoint == "" { + return fmt.Errorf(errorMSGFormat, "Token Endpoint") + } + if config.AuthorizationEndpoint == "" { + return fmt.Errorf(errorMSGFormat, "Authorization Auth Endpoint") + } + if config.Scope == "" { + return fmt.Errorf(errorMSGFormat, "PKCE Auth Scopes") + } + if config.RedirectURLs == nil { + return fmt.Errorf(errorMSGFormat, "PKCE Redirect URLs") + } + return nil +} diff --git a/client/server/server.go b/client/server/server.go index 4633260b2..b7cca947f 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -3,6 +3,7 @@ package server import ( "context" "fmt" + "github.com/netbirdio/netbird/client/internal/auth" "sync" "time" @@ -38,8 +39,8 @@ type Server struct { type oauthAuthFlow struct { expiresAt time.Time - client internal.OAuthClient - info internal.DeviceAuthInfo + flow auth.OAuthFlow + info auth.AuthFlowInfo waitCancel context.CancelFunc } @@ -206,28 +207,15 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro state.Set(internal.StatusConnecting) if msg.SetupKey == "" { - providerConfig, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL) + oAuthFlow, err := auth.NewOAuthFlow(ctx, config) if err != nil { state.Set(internal.StatusLoginFailed) - s, ok := gstatus.FromError(err) - if ok && s.Code() == codes.NotFound { - return nil, gstatus.Errorf(codes.NotFound, "no SSO provider returned from management. "+ - "If you are using hosting Netbird see documentation at "+ - "https://github.com/netbirdio/netbird/tree/main/management for details") - } else if ok && s.Code() == codes.Unimplemented { - return nil, gstatus.Errorf(codes.Unimplemented, "the management server, %s, does not support SSO providers, "+ - "please update your server or use Setup Keys to login", config.ManagementURL) - } else { - log.Errorf("getting device authorization flow info failed with error: %v", err) - return nil, err - } + return nil, err } - hostedClient := internal.NewHostedDeviceFlow(providerConfig.ProviderConfig) - - if s.oauthAuthFlow.client != nil && s.oauthAuthFlow.client.GetClientID(ctx) == hostedClient.GetClientID(context.TODO()) { + if s.oauthAuthFlow.flow != nil && s.oauthAuthFlow.flow.GetClientID(ctx) == oAuthFlow.GetClientID(context.TODO()) { if s.oauthAuthFlow.expiresAt.After(time.Now().Add(90 * time.Second)) { - log.Debugf("using previous device flow info") + log.Debugf("using previous oauth flow info") return &proto.LoginResponse{ NeedsSSOLogin: true, VerificationURI: s.oauthAuthFlow.info.VerificationURI, @@ -242,25 +230,25 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro } } - deviceAuthInfo, err := hostedClient.RequestDeviceCode(context.TODO()) + authInfo, err := oAuthFlow.RequestAuthInfo(context.TODO()) if err != nil { - log.Errorf("getting a request device code failed: %v", err) + log.Errorf("getting a request OAuth flow failed: %v", err) return nil, err } s.mutex.Lock() - s.oauthAuthFlow.client = hostedClient - s.oauthAuthFlow.info = deviceAuthInfo - s.oauthAuthFlow.expiresAt = time.Now().Add(time.Duration(deviceAuthInfo.ExpiresIn) * time.Second) + s.oauthAuthFlow.flow = oAuthFlow + s.oauthAuthFlow.info = authInfo + s.oauthAuthFlow.expiresAt = time.Now().Add(time.Duration(authInfo.ExpiresIn) * time.Second) s.mutex.Unlock() state.Set(internal.StatusNeedsLogin) return &proto.LoginResponse{ NeedsSSOLogin: true, - VerificationURI: deviceAuthInfo.VerificationURI, - VerificationURIComplete: deviceAuthInfo.VerificationURIComplete, - UserCode: deviceAuthInfo.UserCode, + VerificationURI: authInfo.VerificationURI, + VerificationURIComplete: authInfo.VerificationURIComplete, + UserCode: authInfo.UserCode, }, nil } @@ -289,8 +277,8 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin s.actCancel = cancel s.mutex.Unlock() - if s.oauthAuthFlow.client == nil { - return nil, gstatus.Errorf(codes.Internal, "oauth client is not initialized") + if s.oauthAuthFlow.flow == nil { + return nil, gstatus.Errorf(codes.Internal, "oauth flow is not initialized") } state := internal.CtxGetState(ctx) @@ -304,10 +292,10 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin state.Set(internal.StatusConnecting) s.mutex.Lock() - deviceAuthInfo := s.oauthAuthFlow.info + flowInfo := s.oauthAuthFlow.info s.mutex.Unlock() - if deviceAuthInfo.UserCode != msg.UserCode { + if flowInfo.UserCode != msg.UserCode { state.Set(internal.StatusLoginFailed) return nil, gstatus.Errorf(codes.InvalidArgument, "sso user code is invalid") } @@ -324,7 +312,7 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin s.oauthAuthFlow.waitCancel = cancel s.mutex.Unlock() - tokenInfo, err := s.oauthAuthFlow.client.WaitToken(waitCTX, deviceAuthInfo) + tokenInfo, err := s.oauthAuthFlow.flow.WaitToken(waitCTX, flowInfo) if err != nil { if err == context.Canceled { return nil, nil diff --git a/infrastructure_files/base.setup.env b/infrastructure_files/base.setup.env index 6fc85d63d..4bcec128d 100644 --- a/infrastructure_files/base.setup.env +++ b/infrastructure_files/base.setup.env @@ -43,6 +43,10 @@ NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN=${NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN:-f NETBIRD_DISABLE_ANONYMOUS_METRICS=${NETBIRD_DISABLE_ANONYMOUS_METRICS:-false} NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken} +# PKCE authorization flow +NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS=${NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS:-"53000"} +NETBIRD_AUTH_PKCE_USE_ID_TOKEN=${NETBIRD_AUTH_PKCE_USE_ID_TOKEN:-false} + # exports export NETBIRD_DOMAIN export NETBIRD_AUTH_CLIENT_ID @@ -80,4 +84,6 @@ export NETBIRD_AUTH_USER_ID_CLAIM export NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE export NETBIRD_TOKEN_SOURCE export NETBIRD_AUTH_DEVICE_AUTH_SCOPE -export NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN \ No newline at end of file +export NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN +export NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT +export NETBIRD_AUTH_PKCE_USE_ID_TOKEN \ No newline at end of file diff --git a/infrastructure_files/configure.sh b/infrastructure_files/configure.sh index 6b083e29a..477528696 100755 --- a/infrastructure_files/configure.sh +++ b/infrastructure_files/configure.sh @@ -99,12 +99,17 @@ export NETBIRD_AUTH_AUTHORITY=$(jq -r '.issuer' openid-configuration.json) export NETBIRD_AUTH_JWT_CERTS=$(jq -r '.jwks_uri' openid-configuration.json) export NETBIRD_AUTH_TOKEN_ENDPOINT=$(jq -r '.token_endpoint' openid-configuration.json) export NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT=$(jq -r '.device_authorization_endpoint' openid-configuration.json) +export NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT=$(jq -r '.authorization_endpoint' openid-configuration.json) if [[ ! -z "${NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID}" ]]; then # user enabled Device Authorization Grant feature export NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="hosted" fi +if [ "$NETBIRD_TOKEN_SOURCE" = "idToken" ]; then + export NETBIRD_AUTH_PKCE_USE_ID_TOKEN=true +fi + # Check if letsencrypt was disabled if [[ "$NETBIRD_DISABLE_LETSENCRYPT" == "true" ]]; then export NETBIRD_DASHBOARD_ENDPOINT="https://$NETBIRD_DOMAIN:443" @@ -151,6 +156,14 @@ if [ -n "$NETBIRD_MGMT_IDP" ]; then export NETBIRD_IDP_MGMT_EXTRA_CONFIG=$EXTRA_CONFIG fi +IFS=',' read -r -a REDIRECT_URL_PORTS <<< "$NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS" +REDIRECT_URLS="" +for port in "${REDIRECT_URL_PORTS[@]}"; do + REDIRECT_URLS+="\"http://localhost:${port}\"," +done + +export NETBIRD_AUTH_PKCE_REDIRECT_URLS=${REDIRECT_URLS%,} + env | grep NETBIRD envsubst docker-compose.yml diff --git a/infrastructure_files/management.json.tmpl b/infrastructure_files/management.json.tmpl index c2c93a665..e74b93b32 100644 --- a/infrastructure_files/management.json.tmpl +++ b/infrastructure_files/management.json.tmpl @@ -59,5 +59,17 @@ "Scope": "$NETBIRD_AUTH_DEVICE_AUTH_SCOPE", "UseIDToken": $NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN } + }, + "PKCEAuthorizationFlow": { + "ProviderConfig": { + "Audience": "$NETBIRD_AUTH_AUDIENCE", + "ClientID": "$NETBIRD_AUTH_CLIENT_ID", + "ClientSecret": "$NETBIRD_AUTH_CLIENT_SECRET", + "AuthorizationEndpoint": "$NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT", + "TokenEndpoint": "$NETBIRD_AUTH_TOKEN_ENDPOINT", + "Scope": "$NETBIRD_AUTH_SUPPORTED_SCOPES", + "RedirectURLs": [$NETBIRD_AUTH_PKCE_REDIRECT_URLS], + "UseIDToken": $NETBIRD_AUTH_PKCE_USE_ID_TOKEN + } } } diff --git a/infrastructure_files/setup.env.example b/infrastructure_files/setup.env.example index 32523e8d0..9b03ccd2d 100644 --- a/infrastructure_files/setup.env.example +++ b/infrastructure_files/setup.env.example @@ -37,6 +37,12 @@ NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE NETBIRD_AUTH_DEVICE_AUTH_SCOPE="openid" NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN=false # ------------------------------------------- +# OIDC PKCE Authorization Flow +# ------------------------------------------- +# Comma separated port numbers. if already in use, PKCE flow will choose an available port from the list as an alternative +# eg. 53000,54000 +NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS="53000" +# ------------------------------------------- # IDP Management # ------------------------------------------- # eg. zitadel, auth0, azure, keycloak diff --git a/management/client/client.go b/management/client/client.go index d2022f806..db7bd239b 100644 --- a/management/client/client.go +++ b/management/client/client.go @@ -15,5 +15,6 @@ type Client interface { Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error) Login(serverKey wgtypes.Key, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) + GetPKCEAuthorizationFlow(serverKey wgtypes.Key) (*proto.PKCEAuthorizationFlow, error) GetNetworkMap() (*proto.NetworkMap, error) } diff --git a/management/client/client_test.go b/management/client/client_test.go index 41c5a7257..d3d99dc85 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -400,3 +400,49 @@ func Test_GetDeviceAuthorizationFlow(t *testing.T) { assert.Equal(t, expectedFlowInfo.Provider, flowInfo.Provider, "provider should match") assert.Equal(t, expectedFlowInfo.ProviderConfig.ClientID, flowInfo.ProviderConfig.ClientID, "provider configured client ID should match") } + +func Test_GetPKCEAuthorizationFlow(t *testing.T) { + s, lis, mgmtMockServer, serverKey := startMockManagement(t) + defer s.GracefulStop() + + testKey, err := wgtypes.GenerateKey() + if err != nil { + log.Fatal(err) + } + + serverAddr := lis.Addr().String() + ctx := context.Background() + + client, err := NewClient(ctx, serverAddr, testKey, false) + if err != nil { + log.Fatalf("error while creating testClient: %v", err) + } + + expectedFlowInfo := &proto.PKCEAuthorizationFlow{ + ProviderConfig: &proto.ProviderConfig{ + ClientID: "client", + ClientSecret: "secret", + }, + } + + mgmtMockServer.GetPKCEAuthorizationFlowFunc = func(ctx context.Context, req *mgmtProto.EncryptedMessage) (*proto.EncryptedMessage, error) { + encryptedResp, err := encryption.EncryptMessage(serverKey, client.key, expectedFlowInfo) + if err != nil { + return nil, err + } + + return &mgmtProto.EncryptedMessage{ + WgPubKey: serverKey.PublicKey().String(), + Body: encryptedResp, + Version: 0, + }, nil + } + + flowInfo, err := client.GetPKCEAuthorizationFlow(serverKey) + if err != nil { + t.Error("error while retrieving pkce auth flow information") + } + + assert.Equal(t, expectedFlowInfo.ProviderConfig.ClientID, flowInfo.ProviderConfig.ClientID, "provider configured client ID should match") + assert.Equal(t, expectedFlowInfo.ProviderConfig.ClientSecret, flowInfo.ProviderConfig.ClientSecret, "provider configured client secret should match") +} diff --git a/management/client/grpc.go b/management/client/grpc.go index d2ca8c088..e4caed4b0 100644 --- a/management/client/grpc.go +++ b/management/client/grpc.go @@ -366,6 +366,40 @@ func (c *GrpcClient) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.D return flowInfoResp, nil } +// GetPKCEAuthorizationFlow returns a pkce authorization flow information. +// It also takes care of encrypting and decrypting messages. +func (c *GrpcClient) GetPKCEAuthorizationFlow(serverKey wgtypes.Key) (*proto.PKCEAuthorizationFlow, error) { + if !c.ready() { + return nil, fmt.Errorf("no connection to management in order to get pkce authorization flow") + } + mgmCtx, cancel := context.WithTimeout(c.ctx, time.Second*2) + defer cancel() + + message := &proto.PKCEAuthorizationFlowRequest{} + encryptedMSG, err := encryption.EncryptMessage(serverKey, c.key, message) + if err != nil { + return nil, err + } + + resp, err := c.realClient.GetPKCEAuthorizationFlow(mgmCtx, &proto.EncryptedMessage{ + WgPubKey: c.key.PublicKey().String(), + Body: encryptedMSG, + }) + if err != nil { + return nil, err + } + + flowInfoResp := &proto.PKCEAuthorizationFlow{} + err = encryption.DecryptMessage(serverKey, c.key, resp.Body, flowInfoResp) + if err != nil { + errWithMSG := fmt.Errorf("failed to decrypt pkce authorization flow message: %s", err) + log.Error(errWithMSG) + return nil, errWithMSG + } + + return flowInfoResp, nil +} + func (c *GrpcClient) notifyDisconnected() { c.connStateCallbackLock.RLock() defer c.connStateCallbackLock.RUnlock() diff --git a/management/client/mock.go b/management/client/mock.go index ccad538c1..3f2e13cc7 100644 --- a/management/client/mock.go +++ b/management/client/mock.go @@ -13,6 +13,7 @@ type MockClient struct { RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info, sshKey []byte) (*proto.LoginResponse, error) LoginFunc func(serverKey wgtypes.Key, info *system.Info, sshKey []byte) (*proto.LoginResponse, error) GetDeviceAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) + GetPKCEAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.PKCEAuthorizationFlow, error) } func (m *MockClient) Close() error { @@ -57,6 +58,13 @@ func (m *MockClient) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.D return m.GetDeviceAuthorizationFlowFunc(serverKey) } +func (m *MockClient) GetPKCEAuthorizationFlow(serverKey wgtypes.Key) (*proto.PKCEAuthorizationFlow, error) { + if m.GetPKCEAuthorizationFlowFunc == nil { + return nil, nil + } + return m.GetPKCEAuthorizationFlow(serverKey) +} + // GetNetworkMap mock implementation of GetNetworkMap from mgm.Client interface func (m *MockClient) GetNetworkMap() (*proto.NetworkMap, error) { return nil, nil diff --git a/management/cmd/management.go b/management/cmd/management.go index 842dcbaa2..7cbc2bb96 100644 --- a/management/cmd/management.go +++ b/management/cmd/management.go @@ -426,6 +426,15 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) { config.DeviceAuthorizationFlow.ProviderConfig.Scope = server.DefaultDeviceAuthFlowScope } } + + if config.PKCEAuthorizationFlow != nil { + log.Infof("overriding PKCEAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s", + oidcConfig.TokenEndpoint, config.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint) + config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint + log.Infof("overriding PKCEAuthorizationFlow.AuthorizationEndpoint with a new value: %s, previously configured value: %s", + oidcConfig.AuthorizationEndpoint, config.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint) + config.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint = oidcConfig.AuthorizationEndpoint + } } return config, err @@ -433,10 +442,11 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) { // OIDCConfigResponse used for parsing OIDC config response type OIDCConfigResponse struct { - Issuer string `json:"issuer"` - TokenEndpoint string `json:"token_endpoint"` - DeviceAuthEndpoint string `json:"device_authorization_endpoint"` - JwksURI string `json:"jwks_uri"` + Issuer string `json:"issuer"` + TokenEndpoint string `json:"token_endpoint"` + DeviceAuthEndpoint string `json:"device_authorization_endpoint"` + JwksURI string `json:"jwks_uri"` + AuthorizationEndpoint string `json:"authorization_endpoint"` } // fetchOIDCConfig fetches OIDC configuration from the IDP diff --git a/management/proto/management.pb.go b/management/proto/management.pb.go index 250e3db05..eb80f9299 100644 --- a/management/proto/management.pb.go +++ b/management/proto/management.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.21.9 +// protoc v3.21.12 // source: management.proto package proto @@ -162,7 +162,7 @@ func (x FirewallRuleDirection) Number() protoreflect.EnumNumber { // Deprecated: Use FirewallRuleDirection.Descriptor instead. func (FirewallRuleDirection) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{25, 0} + return file_management_proto_rawDescGZIP(), []int{27, 0} } type FirewallRuleAction int32 @@ -208,7 +208,7 @@ func (x FirewallRuleAction) Number() protoreflect.EnumNumber { // Deprecated: Use FirewallRuleAction.Descriptor instead. func (FirewallRuleAction) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{25, 1} + return file_management_proto_rawDescGZIP(), []int{27, 1} } type FirewallRuleProtocol int32 @@ -263,7 +263,7 @@ func (x FirewallRuleProtocol) Number() protoreflect.EnumNumber { // Deprecated: Use FirewallRuleProtocol.Descriptor instead. func (FirewallRuleProtocol) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{25, 2} + return file_management_proto_rawDescGZIP(), []int{27, 2} } type EncryptedMessage struct { @@ -1477,7 +1477,96 @@ func (x *DeviceAuthorizationFlow) GetProviderConfig() *ProviderConfig { return nil } -// ProviderConfig has all attributes needed to initiate a device authorization flow +// PKCEAuthorizationFlowRequest empty struct for future expansion +type PKCEAuthorizationFlowRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PKCEAuthorizationFlowRequest) Reset() { + *x = PKCEAuthorizationFlowRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PKCEAuthorizationFlowRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PKCEAuthorizationFlowRequest) ProtoMessage() {} + +func (x *PKCEAuthorizationFlowRequest) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[18] + 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 PKCEAuthorizationFlowRequest.ProtoReflect.Descriptor instead. +func (*PKCEAuthorizationFlowRequest) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{18} +} + +// PKCEAuthorizationFlow represents Authorization Code Flow information +// that can be used by the client to login initiate a Oauth 2.0 authorization code grant flow +// with Proof Key for Code Exchange (PKCE). See https://datatracker.ietf.org/doc/html/rfc7636 +type PKCEAuthorizationFlow struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProviderConfig *ProviderConfig `protobuf:"bytes,1,opt,name=ProviderConfig,proto3" json:"ProviderConfig,omitempty"` +} + +func (x *PKCEAuthorizationFlow) Reset() { + *x = PKCEAuthorizationFlow{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PKCEAuthorizationFlow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PKCEAuthorizationFlow) ProtoMessage() {} + +func (x *PKCEAuthorizationFlow) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[19] + 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 PKCEAuthorizationFlow.ProtoReflect.Descriptor instead. +func (*PKCEAuthorizationFlow) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{19} +} + +func (x *PKCEAuthorizationFlow) GetProviderConfig() *ProviderConfig { + if x != nil { + return x.ProviderConfig + } + return nil +} + +// ProviderConfig has all attributes needed to initiate a device/pkce authorization flow type ProviderConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1500,12 +1589,16 @@ type ProviderConfig struct { Scope string `protobuf:"bytes,7,opt,name=Scope,proto3" json:"Scope,omitempty"` // UseIDToken indicates if the id token should be used for authentication UseIDToken bool `protobuf:"varint,8,opt,name=UseIDToken,proto3" json:"UseIDToken,omitempty"` + // AuthorizationEndpoint is the endpoint of an IDP manager where clients can obtain authorization code. + AuthorizationEndpoint string `protobuf:"bytes,9,opt,name=AuthorizationEndpoint,proto3" json:"AuthorizationEndpoint,omitempty"` + // RedirectURLs handles authorization code from IDP manager + RedirectURLs []string `protobuf:"bytes,10,rep,name=RedirectURLs,proto3" json:"RedirectURLs,omitempty"` } func (x *ProviderConfig) Reset() { *x = ProviderConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[18] + mi := &file_management_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1518,7 +1611,7 @@ func (x *ProviderConfig) String() string { func (*ProviderConfig) ProtoMessage() {} func (x *ProviderConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[18] + mi := &file_management_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1531,7 +1624,7 @@ func (x *ProviderConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderConfig.ProtoReflect.Descriptor instead. func (*ProviderConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{18} + return file_management_proto_rawDescGZIP(), []int{20} } func (x *ProviderConfig) GetClientID() string { @@ -1590,6 +1683,20 @@ func (x *ProviderConfig) GetUseIDToken() bool { return false } +func (x *ProviderConfig) GetAuthorizationEndpoint() string { + if x != nil { + return x.AuthorizationEndpoint + } + return "" +} + +func (x *ProviderConfig) GetRedirectURLs() []string { + if x != nil { + return x.RedirectURLs + } + return nil +} + // Route represents a route.Route object type Route struct { state protoimpl.MessageState @@ -1608,7 +1715,7 @@ type Route struct { func (x *Route) Reset() { *x = Route{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[19] + mi := &file_management_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1621,7 +1728,7 @@ func (x *Route) String() string { func (*Route) ProtoMessage() {} func (x *Route) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[19] + mi := &file_management_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1634,7 +1741,7 @@ func (x *Route) ProtoReflect() protoreflect.Message { // Deprecated: Use Route.ProtoReflect.Descriptor instead. func (*Route) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{19} + return file_management_proto_rawDescGZIP(), []int{21} } func (x *Route) GetID() string { @@ -1700,7 +1807,7 @@ type DNSConfig struct { func (x *DNSConfig) Reset() { *x = DNSConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[20] + mi := &file_management_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1713,7 +1820,7 @@ func (x *DNSConfig) String() string { func (*DNSConfig) ProtoMessage() {} func (x *DNSConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[20] + mi := &file_management_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1726,7 +1833,7 @@ func (x *DNSConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use DNSConfig.ProtoReflect.Descriptor instead. func (*DNSConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{20} + return file_management_proto_rawDescGZIP(), []int{22} } func (x *DNSConfig) GetServiceEnable() bool { @@ -1763,7 +1870,7 @@ type CustomZone struct { func (x *CustomZone) Reset() { *x = CustomZone{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[21] + mi := &file_management_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1776,7 +1883,7 @@ func (x *CustomZone) String() string { func (*CustomZone) ProtoMessage() {} func (x *CustomZone) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[21] + mi := &file_management_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1789,7 +1896,7 @@ func (x *CustomZone) ProtoReflect() protoreflect.Message { // Deprecated: Use CustomZone.ProtoReflect.Descriptor instead. func (*CustomZone) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{21} + return file_management_proto_rawDescGZIP(), []int{23} } func (x *CustomZone) GetDomain() string { @@ -1822,7 +1929,7 @@ type SimpleRecord struct { func (x *SimpleRecord) Reset() { *x = SimpleRecord{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[22] + mi := &file_management_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1835,7 +1942,7 @@ func (x *SimpleRecord) String() string { func (*SimpleRecord) ProtoMessage() {} func (x *SimpleRecord) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[22] + mi := &file_management_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1848,7 +1955,7 @@ func (x *SimpleRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use SimpleRecord.ProtoReflect.Descriptor instead. func (*SimpleRecord) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{22} + return file_management_proto_rawDescGZIP(), []int{24} } func (x *SimpleRecord) GetName() string { @@ -1900,7 +2007,7 @@ type NameServerGroup struct { func (x *NameServerGroup) Reset() { *x = NameServerGroup{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[23] + mi := &file_management_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1913,7 +2020,7 @@ func (x *NameServerGroup) String() string { func (*NameServerGroup) ProtoMessage() {} func (x *NameServerGroup) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[23] + mi := &file_management_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1926,7 +2033,7 @@ func (x *NameServerGroup) ProtoReflect() protoreflect.Message { // Deprecated: Use NameServerGroup.ProtoReflect.Descriptor instead. func (*NameServerGroup) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{23} + return file_management_proto_rawDescGZIP(), []int{25} } func (x *NameServerGroup) GetNameServers() []*NameServer { @@ -1964,7 +2071,7 @@ type NameServer struct { func (x *NameServer) Reset() { *x = NameServer{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[24] + mi := &file_management_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1977,7 +2084,7 @@ func (x *NameServer) String() string { func (*NameServer) ProtoMessage() {} func (x *NameServer) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[24] + mi := &file_management_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1990,7 +2097,7 @@ func (x *NameServer) ProtoReflect() protoreflect.Message { // Deprecated: Use NameServer.ProtoReflect.Descriptor instead. func (*NameServer) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{24} + return file_management_proto_rawDescGZIP(), []int{26} } func (x *NameServer) GetIP() string { @@ -2030,7 +2137,7 @@ type FirewallRule struct { func (x *FirewallRule) Reset() { *x = FirewallRule{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[25] + mi := &file_management_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2043,7 +2150,7 @@ func (x *FirewallRule) String() string { func (*FirewallRule) ProtoMessage() {} func (x *FirewallRule) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[25] + mi := &file_management_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2056,7 +2163,7 @@ func (x *FirewallRule) ProtoReflect() protoreflect.Message { // Deprecated: Use FirewallRule.ProtoReflect.Descriptor instead. func (*FirewallRule) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{25} + return file_management_proto_rawDescGZIP(), []int{27} } func (x *FirewallRule) GetPeerIP() string { @@ -2270,121 +2377,140 @@ var file_management_proto_rawDesc = []byte{ 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x4f, - 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x90, 0x02, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, - 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, - 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x55, 0x73, 0x65, - 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x55, - 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, - 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, - 0x65, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, - 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, - 0x65, 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, - 0x44, 0x22, 0xb4, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, - 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, - 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, - 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32, - 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69, 0x6d, - 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x73, 0x22, 0x74, 0x0a, 0x0c, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, - 0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, - 0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22, 0x7f, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, - 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, - 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, - 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, - 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, - 0x6f, 0x72, 0x74, 0x22, 0xf0, 0x02, 0x0a, 0x0c, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, - 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, - 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, - 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, - 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, - 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, - 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, - 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, - 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, - 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, - 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, - 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x32, 0xf7, 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, - 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, - 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, - 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5b, 0x0a, 0x15, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, + 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x22, 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, + 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, + 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x49, 0x44, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x55, 0x73, 0x65, 0x49, + 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, + 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x18, 0x0a, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, + 0x22, 0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, + 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, + 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, + 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x22, 0xb4, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x10, + 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, + 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, + 0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x22, + 0x58, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x74, 0x0a, 0x0c, 0x53, 0x69, 0x6d, + 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61, + 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22, + 0x7f, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, + 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, + 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, + 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0e, + 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, + 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, + 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xf0, 0x02, 0x0a, 0x0c, 0x46, + 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50, + 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, + 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, + 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, + 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, + 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, + 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72, + 0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, + 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x22, + 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, + 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x22, + 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, + 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, + 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x32, 0xd1, 0x03, + 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, + 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, + 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, - 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, + 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, + 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x4b, + 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, + 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -2400,7 +2526,7 @@ func file_management_proto_rawDescGZIP() []byte { } var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 5) -var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 26) +var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 28) var file_management_proto_goTypes = []interface{}{ (HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol (DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider @@ -2425,15 +2551,17 @@ var file_management_proto_goTypes = []interface{}{ (*SSHConfig)(nil), // 20: management.SSHConfig (*DeviceAuthorizationFlowRequest)(nil), // 21: management.DeviceAuthorizationFlowRequest (*DeviceAuthorizationFlow)(nil), // 22: management.DeviceAuthorizationFlow - (*ProviderConfig)(nil), // 23: management.ProviderConfig - (*Route)(nil), // 24: management.Route - (*DNSConfig)(nil), // 25: management.DNSConfig - (*CustomZone)(nil), // 26: management.CustomZone - (*SimpleRecord)(nil), // 27: management.SimpleRecord - (*NameServerGroup)(nil), // 28: management.NameServerGroup - (*NameServer)(nil), // 29: management.NameServer - (*FirewallRule)(nil), // 30: management.FirewallRule - (*timestamppb.Timestamp)(nil), // 31: google.protobuf.Timestamp + (*PKCEAuthorizationFlowRequest)(nil), // 23: management.PKCEAuthorizationFlowRequest + (*PKCEAuthorizationFlow)(nil), // 24: management.PKCEAuthorizationFlow + (*ProviderConfig)(nil), // 25: management.ProviderConfig + (*Route)(nil), // 26: management.Route + (*DNSConfig)(nil), // 27: management.DNSConfig + (*CustomZone)(nil), // 28: management.CustomZone + (*SimpleRecord)(nil), // 29: management.SimpleRecord + (*NameServerGroup)(nil), // 30: management.NameServerGroup + (*NameServer)(nil), // 31: management.NameServer + (*FirewallRule)(nil), // 32: management.FirewallRule + (*timestamppb.Timestamp)(nil), // 33: google.protobuf.Timestamp } var file_management_proto_depIdxs = []int32{ 14, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig @@ -2444,7 +2572,7 @@ var file_management_proto_depIdxs = []int32{ 9, // 5: management.LoginRequest.peerKeys:type_name -> management.PeerKeys 14, // 6: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig 17, // 7: management.LoginResponse.peerConfig:type_name -> management.PeerConfig - 31, // 8: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp + 33, // 8: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp 15, // 9: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig 16, // 10: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig 15, // 11: management.WiretrusteeConfig.signal:type_name -> management.HostConfig @@ -2453,35 +2581,38 @@ var file_management_proto_depIdxs = []int32{ 20, // 14: management.PeerConfig.sshConfig:type_name -> management.SSHConfig 17, // 15: management.NetworkMap.peerConfig:type_name -> management.PeerConfig 19, // 16: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig - 24, // 17: management.NetworkMap.Routes:type_name -> management.Route - 25, // 18: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig + 26, // 17: management.NetworkMap.Routes:type_name -> management.Route + 27, // 18: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig 19, // 19: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig - 30, // 20: management.NetworkMap.FirewallRules:type_name -> management.FirewallRule + 32, // 20: management.NetworkMap.FirewallRules:type_name -> management.FirewallRule 20, // 21: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig 1, // 22: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider - 23, // 23: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig - 28, // 24: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup - 26, // 25: management.DNSConfig.CustomZones:type_name -> management.CustomZone - 27, // 26: management.CustomZone.Records:type_name -> management.SimpleRecord - 29, // 27: management.NameServerGroup.NameServers:type_name -> management.NameServer - 2, // 28: management.FirewallRule.Direction:type_name -> management.FirewallRule.direction - 3, // 29: management.FirewallRule.Action:type_name -> management.FirewallRule.action - 4, // 30: management.FirewallRule.Protocol:type_name -> management.FirewallRule.protocol - 5, // 31: management.ManagementService.Login:input_type -> management.EncryptedMessage - 5, // 32: management.ManagementService.Sync:input_type -> management.EncryptedMessage - 13, // 33: management.ManagementService.GetServerKey:input_type -> management.Empty - 13, // 34: management.ManagementService.isHealthy:input_type -> management.Empty - 5, // 35: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage - 5, // 36: management.ManagementService.Login:output_type -> management.EncryptedMessage - 5, // 37: management.ManagementService.Sync:output_type -> management.EncryptedMessage - 12, // 38: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse - 13, // 39: management.ManagementService.isHealthy:output_type -> management.Empty - 5, // 40: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage - 36, // [36:41] is the sub-list for method output_type - 31, // [31:36] is the sub-list for method input_type - 31, // [31:31] is the sub-list for extension type_name - 31, // [31:31] is the sub-list for extension extendee - 0, // [0:31] is the sub-list for field type_name + 25, // 23: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig + 25, // 24: management.PKCEAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig + 30, // 25: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup + 28, // 26: management.DNSConfig.CustomZones:type_name -> management.CustomZone + 29, // 27: management.CustomZone.Records:type_name -> management.SimpleRecord + 31, // 28: management.NameServerGroup.NameServers:type_name -> management.NameServer + 2, // 29: management.FirewallRule.Direction:type_name -> management.FirewallRule.direction + 3, // 30: management.FirewallRule.Action:type_name -> management.FirewallRule.action + 4, // 31: management.FirewallRule.Protocol:type_name -> management.FirewallRule.protocol + 5, // 32: management.ManagementService.Login:input_type -> management.EncryptedMessage + 5, // 33: management.ManagementService.Sync:input_type -> management.EncryptedMessage + 13, // 34: management.ManagementService.GetServerKey:input_type -> management.Empty + 13, // 35: management.ManagementService.isHealthy:input_type -> management.Empty + 5, // 36: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage + 5, // 37: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage + 5, // 38: management.ManagementService.Login:output_type -> management.EncryptedMessage + 5, // 39: management.ManagementService.Sync:output_type -> management.EncryptedMessage + 12, // 40: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse + 13, // 41: management.ManagementService.isHealthy:output_type -> management.Empty + 5, // 42: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage + 5, // 43: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage + 38, // [38:44] is the sub-list for method output_type + 32, // [32:38] is the sub-list for method input_type + 32, // [32:32] is the sub-list for extension type_name + 32, // [32:32] is the sub-list for extension extendee + 0, // [0:32] is the sub-list for field type_name } func init() { file_management_proto_init() } @@ -2707,7 +2838,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProviderConfig); i { + switch v := v.(*PKCEAuthorizationFlowRequest); i { case 0: return &v.state case 1: @@ -2719,7 +2850,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Route); i { + switch v := v.(*PKCEAuthorizationFlow); i { case 0: return &v.state case 1: @@ -2731,7 +2862,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DNSConfig); i { + switch v := v.(*ProviderConfig); i { case 0: return &v.state case 1: @@ -2743,7 +2874,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CustomZone); i { + switch v := v.(*Route); i { case 0: return &v.state case 1: @@ -2755,7 +2886,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SimpleRecord); i { + switch v := v.(*DNSConfig); i { case 0: return &v.state case 1: @@ -2767,7 +2898,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NameServerGroup); i { + switch v := v.(*CustomZone); i { case 0: return &v.state case 1: @@ -2779,7 +2910,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NameServer); i { + switch v := v.(*SimpleRecord); i { case 0: return &v.state case 1: @@ -2791,6 +2922,30 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NameServerGroup); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_management_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NameServer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_management_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FirewallRule); i { case 0: return &v.state @@ -2809,7 +2964,7 @@ func file_management_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_management_proto_rawDesc, NumEnums: 5, - NumMessages: 26, + NumMessages: 28, NumExtensions: 0, NumServices: 1, }, diff --git a/management/proto/management.proto b/management/proto/management.proto index e1c0fee37..d5b925d73 100644 --- a/management/proto/management.proto +++ b/management/proto/management.proto @@ -31,6 +31,13 @@ service ManagementService { // EncryptedMessage of the request has a body of DeviceAuthorizationFlowRequest. // EncryptedMessage of the response has a body of DeviceAuthorizationFlow. rpc GetDeviceAuthorizationFlow(EncryptedMessage) returns (EncryptedMessage) {} + + // Exposes a PKCE authorization code flow information + // This is used for initiating a Oauth 2 authorization grant flow + // with Proof Key for Code Exchange (PKCE) which will be used by our clients to Login. + // EncryptedMessage of the request has a body of PKCEAuthorizationFlowRequest. + // EncryptedMessage of the response has a body of PKCEAuthorizationFlow. + rpc GetPKCEAuthorizationFlow(EncryptedMessage) returns (EncryptedMessage) {} } message EncryptedMessage { @@ -237,7 +244,17 @@ message DeviceAuthorizationFlow { } } -// ProviderConfig has all attributes needed to initiate a device authorization flow +// PKCEAuthorizationFlowRequest empty struct for future expansion +message PKCEAuthorizationFlowRequest {} + +// PKCEAuthorizationFlow represents Authorization Code Flow information +// that can be used by the client to login initiate a Oauth 2.0 authorization code grant flow +// with Proof Key for Code Exchange (PKCE). See https://datatracker.ietf.org/doc/html/rfc7636 +message PKCEAuthorizationFlow { + ProviderConfig ProviderConfig = 1; +} + +// ProviderConfig has all attributes needed to initiate a device/pkce authorization flow message ProviderConfig { // An IDP application client id string ClientID = 1; @@ -256,6 +273,10 @@ message ProviderConfig { string Scope = 7; // UseIDToken indicates if the id token should be used for authentication bool UseIDToken = 8; + // AuthorizationEndpoint is the endpoint of an IDP manager where clients can obtain authorization code. + string AuthorizationEndpoint = 9; + // RedirectURLs handles authorization code from IDP manager + repeated string RedirectURLs = 10; } // Route represents a route.Route object diff --git a/management/proto/management_grpc.pb.go b/management/proto/management_grpc.pb.go index a7d306bb8..5e2bcd225 100644 --- a/management/proto/management_grpc.pb.go +++ b/management/proto/management_grpc.pb.go @@ -37,6 +37,12 @@ type ManagementServiceClient interface { // EncryptedMessage of the request has a body of DeviceAuthorizationFlowRequest. // EncryptedMessage of the response has a body of DeviceAuthorizationFlow. GetDeviceAuthorizationFlow(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) + // Exposes a PKCE authorization code flow information + // This is used for initiating a Oauth 2 authorization grant flow + // with Proof Key for Code Exchange (PKCE) which will be used by our clients to Login. + // EncryptedMessage of the request has a body of PKCEAuthorizationFlowRequest. + // EncryptedMessage of the response has a body of PKCEAuthorizationFlow. + GetPKCEAuthorizationFlow(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) } type managementServiceClient struct { @@ -115,6 +121,15 @@ func (c *managementServiceClient) GetDeviceAuthorizationFlow(ctx context.Context return out, nil } +func (c *managementServiceClient) GetPKCEAuthorizationFlow(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) { + out := new(EncryptedMessage) + err := c.cc.Invoke(ctx, "/management.ManagementService/GetPKCEAuthorizationFlow", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // ManagementServiceServer is the server API for ManagementService service. // All implementations must embed UnimplementedManagementServiceServer // for forward compatibility @@ -138,6 +153,12 @@ type ManagementServiceServer interface { // EncryptedMessage of the request has a body of DeviceAuthorizationFlowRequest. // EncryptedMessage of the response has a body of DeviceAuthorizationFlow. GetDeviceAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) + // Exposes a PKCE authorization code flow information + // This is used for initiating a Oauth 2 authorization grant flow + // with Proof Key for Code Exchange (PKCE) which will be used by our clients to Login. + // EncryptedMessage of the request has a body of PKCEAuthorizationFlowRequest. + // EncryptedMessage of the response has a body of PKCEAuthorizationFlow. + GetPKCEAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) mustEmbedUnimplementedManagementServiceServer() } @@ -160,6 +181,9 @@ func (UnimplementedManagementServiceServer) IsHealthy(context.Context, *Empty) ( func (UnimplementedManagementServiceServer) GetDeviceAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) { return nil, status.Errorf(codes.Unimplemented, "method GetDeviceAuthorizationFlow not implemented") } +func (UnimplementedManagementServiceServer) GetPKCEAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPKCEAuthorizationFlow not implemented") +} func (UnimplementedManagementServiceServer) mustEmbedUnimplementedManagementServiceServer() {} // UnsafeManagementServiceServer may be embedded to opt out of forward compatibility for this service. @@ -266,6 +290,24 @@ func _ManagementService_GetDeviceAuthorizationFlow_Handler(srv interface{}, ctx return interceptor(ctx, in, info, handler) } +func _ManagementService_GetPKCEAuthorizationFlow_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EncryptedMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ManagementServiceServer).GetPKCEAuthorizationFlow(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/management.ManagementService/GetPKCEAuthorizationFlow", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ManagementServiceServer).GetPKCEAuthorizationFlow(ctx, req.(*EncryptedMessage)) + } + return interceptor(ctx, in, info, handler) +} + // ManagementService_ServiceDesc is the grpc.ServiceDesc for ManagementService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -289,6 +331,10 @@ var ManagementService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetDeviceAuthorizationFlow", Handler: _ManagementService_GetDeviceAuthorizationFlow_Handler, }, + { + MethodName: "GetPKCEAuthorizationFlow", + Handler: _ManagementService_GetPKCEAuthorizationFlow_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/management/server/config.go b/management/server/config.go index 32a468e91..ea0143988 100644 --- a/management/server/config.go +++ b/management/server/config.go @@ -42,6 +42,8 @@ type Config struct { IdpManagerConfig *idp.Config DeviceAuthorizationFlow *DeviceAuthorizationFlow + + PKCEAuthorizationFlow *PKCEAuthorizationFlow } // GetAuthAudiences returns the audience from the http config and device authorization flow config @@ -101,7 +103,14 @@ type DeviceAuthorizationFlow struct { ProviderConfig ProviderConfig } -// ProviderConfig has all attributes needed to initiate a device authorization flow +// PKCEAuthorizationFlow represents Authorization Code Flow information +// that can be used by the client to login initiate a Oauth 2.0 authorization code grant flow +// with Proof Key for Code Exchange (PKCE). See https://datatracker.ietf.org/doc/html/rfc7636 +type PKCEAuthorizationFlow struct { + ProviderConfig ProviderConfig +} + +// ProviderConfig has all attributes needed to initiate a device/pkce authorization flow type ProviderConfig struct { // ClientID An IDP application client id ClientID string @@ -116,10 +125,14 @@ type ProviderConfig struct { TokenEndpoint string // DeviceAuthEndpoint is the endpoint of an IDP manager where clients can obtain device authorization code DeviceAuthEndpoint string + // AuthorizationEndpoint is the endpoint of an IDP manager where clients can obtain authorization code + AuthorizationEndpoint string // Scopes provides the scopes to be included in the token request Scope string // UseIDToken indicates if the id token should be used for authentication UseIDToken bool + // RedirectURL handles authorization code from IDP manager + RedirectURLs []string } // validateURL validates input http url diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index a2a0caf5a..94cb1de9d 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -543,3 +543,49 @@ func (s *GRPCServer) GetDeviceAuthorizationFlow(ctx context.Context, req *proto. Body: encryptedResp, }, nil } + +// GetPKCEAuthorizationFlow returns a pkce authorization flow information +// This is used for initiating an Oauth 2 pkce authorization grant flow +// which will be used by our clients to Login +func (s *GRPCServer) GetPKCEAuthorizationFlow(_ context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) { + peerKey, err := wgtypes.ParseKey(req.GetWgPubKey()) + if err != nil { + errMSG := fmt.Sprintf("error while parsing peer's Wireguard public key %s on GetPKCEAuthorizationFlow request.", req.WgPubKey) + log.Warn(errMSG) + return nil, status.Error(codes.InvalidArgument, errMSG) + } + + err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, &proto.PKCEAuthorizationFlowRequest{}) + if err != nil { + errMSG := fmt.Sprintf("error while decrypting peer's message with Wireguard public key %s.", req.WgPubKey) + log.Warn(errMSG) + return nil, status.Error(codes.InvalidArgument, errMSG) + } + + if s.config.PKCEAuthorizationFlow == nil { + return nil, status.Error(codes.NotFound, "no pkce authorization flow information available") + } + + flowInfoResp := &proto.PKCEAuthorizationFlow{ + ProviderConfig: &proto.ProviderConfig{ + Audience: s.config.PKCEAuthorizationFlow.ProviderConfig.Audience, + ClientID: s.config.PKCEAuthorizationFlow.ProviderConfig.ClientID, + ClientSecret: s.config.PKCEAuthorizationFlow.ProviderConfig.ClientSecret, + TokenEndpoint: s.config.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint, + AuthorizationEndpoint: s.config.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint, + Scope: s.config.PKCEAuthorizationFlow.ProviderConfig.Scope, + RedirectURLs: s.config.PKCEAuthorizationFlow.ProviderConfig.RedirectURLs, + UseIDToken: s.config.PKCEAuthorizationFlow.ProviderConfig.UseIDToken, + }, + } + + encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, flowInfoResp) + if err != nil { + return nil, status.Error(codes.Internal, "failed to encrypt no pkce authorization flow information") + } + + return &proto.EncryptedMessage{ + WgPubKey: s.wgKey.PublicKey().String(), + Body: encryptedResp, + }, nil +} diff --git a/management/server/mock_server/management_server_mock.go b/management/server/mock_server/management_server_mock.go index 97ae5f328..29544b53f 100644 --- a/management/server/mock_server/management_server_mock.go +++ b/management/server/mock_server/management_server_mock.go @@ -16,6 +16,7 @@ type ManagementServiceServerMock struct { GetServerKeyFunc func(context.Context, *proto.Empty) (*proto.ServerKeyResponse, error) IsHealthyFunc func(context.Context, *proto.Empty) (*proto.Empty, error) GetDeviceAuthorizationFlowFunc func(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) + GetPKCEAuthorizationFlowFunc func(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) } func (m ManagementServiceServerMock) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) { @@ -52,3 +53,10 @@ func (m ManagementServiceServerMock) GetDeviceAuthorizationFlow(ctx context.Cont } return nil, status.Errorf(codes.Unimplemented, "method GetDeviceAuthorizationFlow not implemented") } + +func (m ManagementServiceServerMock) GetPKCEAuthorizationFlow(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) { + if m.GetPKCEAuthorizationFlowFunc != nil { + return m.GetPKCEAuthorizationFlowFunc(ctx, req) + } + return nil, status.Errorf(codes.Unimplemented, "method GetPKCEAuthorizationFlow not implemented") +}