mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-19 00:36:38 +00:00
Compare commits
12 Commits
v0.21.11
...
update-get
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9fa28d8a0 | ||
|
|
144ac868e0 | ||
|
|
b70339d3bd | ||
|
|
ee890971a3 | ||
|
|
eeb1b619b7 | ||
|
|
4b47f6b23c | ||
|
|
64f6343fcc | ||
|
|
24713fbe59 | ||
|
|
7794b744f8 | ||
|
|
0d0c30c16d | ||
|
|
b0364da67c | ||
|
|
5883e019c9 |
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -7,6 +7,16 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'go.mod'
|
||||||
|
- 'go.sum'
|
||||||
|
- '.goreleaser.yml'
|
||||||
|
- '.goreleaser_ui.yaml'
|
||||||
|
- '.goreleaser_ui_darwin.yaml'
|
||||||
|
- '.github/workflows/release.yml'
|
||||||
|
- 'release_files/**'
|
||||||
|
- '**/Dockerfile'
|
||||||
|
- '**/Dockerfile.*'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
SIGN_PIPE_VER: "v0.0.8"
|
SIGN_PIPE_VER: "v0.0.8"
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
name: Test Docker Compose Linux
|
name: Test Infrastructure files
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'infrastructure_files/**'
|
||||||
|
- '.github/workflows/test-infrastructure-files.yml'
|
||||||
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
@@ -12,7 +15,7 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test-docker-compose:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install jq
|
- name: Install jq
|
||||||
@@ -35,7 +38,7 @@ jobs:
|
|||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: cp setup.env
|
- name: cp setup.env
|
||||||
run: cp infrastructure_files/tests/setup.env infrastructure_files/
|
run: cp infrastructure_files/tests/setup.env infrastructure_files/
|
||||||
@@ -69,6 +72,7 @@ jobs:
|
|||||||
CI_NETBIRD_AUTH_JWT_CERTS: https://example.eu.auth0.com/.well-known/jwks.json
|
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_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_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_AUTH_REDIRECT_URI: "/peers"
|
||||||
CI_NETBIRD_TOKEN_SOURCE: "idToken"
|
CI_NETBIRD_TOKEN_SOURCE: "idToken"
|
||||||
CI_NETBIRD_AUTH_USER_ID_CLAIM: "email"
|
CI_NETBIRD_AUTH_USER_ID_CLAIM: "email"
|
||||||
@@ -91,8 +95,8 @@ jobs:
|
|||||||
grep LETSENCRYPT_DOMAIN docker-compose.yml | egrep 'LETSENCRYPT_DOMAIN=$'
|
grep LETSENCRYPT_DOMAIN docker-compose.yml | egrep 'LETSENCRYPT_DOMAIN=$'
|
||||||
grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE
|
grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE
|
||||||
grep AuthUserIDClaim management.json | grep $CI_NETBIRD_AUTH_USER_ID_CLAIM
|
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 -A 3 DeviceAuthorizationFlow management.json | grep -A 1 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE
|
||||||
grep Scope management.json | grep "$CI_NETBIRD_AUTH_DEVICE_AUTH_SCOPE"
|
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 UseIDToken management.json | grep false
|
||||||
grep -A 1 IdpManagerConfig management.json | grep ManagerType | grep $CI_NETBIRD_MGMT_IDP
|
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
|
grep -A 3 IdpManagerConfig management.json | grep -A 1 ClientConfig | grep Issuer | grep $CI_NETBIRD_AUTH_AUTHORITY
|
||||||
@@ -100,6 +104,12 @@ jobs:
|
|||||||
grep -A 5 IdpManagerConfig management.json | grep -A 3 ClientConfig | grep ClientID | grep $CI_NETBIRD_IDP_MGMT_CLIENT_ID
|
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 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 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
|
- name: run docker compose up
|
||||||
working-directory: infrastructure_files
|
working-directory: infrastructure_files
|
||||||
@@ -114,3 +124,28 @@ jobs:
|
|||||||
count=$(docker compose ps --format json | jq '.[] | select(.Project | contains("infrastructure_files")) | .State' | grep -c running)
|
count=$(docker compose ps --format json | jq '.[] | select(.Project | contains("infrastructure_files")) | .State' | grep -c running)
|
||||||
test $count -eq 4
|
test $count -eq 4
|
||||||
working-directory: infrastructure_files
|
working-directory: infrastructure_files
|
||||||
|
|
||||||
|
test-getting-started-script:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install jq
|
||||||
|
run: sudo apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: run script
|
||||||
|
run: bash -x infrastructure_files/getting-started-with-zitadel.sh
|
||||||
|
|
||||||
|
- name: test Caddy file gen
|
||||||
|
run: test -f Caddyfile
|
||||||
|
- name: test docker-compose file gen
|
||||||
|
run: test -f docker-compose.yml
|
||||||
|
- name: test management.json file gen
|
||||||
|
run: test -f management.json
|
||||||
|
- name: test turnserver.conf file gen
|
||||||
|
run: test -f turnserver.conf
|
||||||
|
- name: test zitadel.env file gen
|
||||||
|
run: test -f zitadel.env
|
||||||
|
- name: test dashboard.env file gen
|
||||||
|
run: test -f dashboard.env
|
||||||
@@ -377,3 +377,8 @@ uploads:
|
|||||||
target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
||||||
username: dev@wiretrustee.com
|
username: dev@wiretrustee.com
|
||||||
method: PUT
|
method: PUT
|
||||||
|
|
||||||
|
release:
|
||||||
|
extra_files:
|
||||||
|
- glob: ./infrastructure_files/getting-started-with-zitadel.sh
|
||||||
|
- glob: ./release_files/install.sh
|
||||||
@@ -57,9 +57,10 @@ NetBird uses [NAT traversal techniques](https://en.wikipedia.org/wiki/Interactiv
|
|||||||
- \[x] Network Routes.
|
- \[x] Network Routes.
|
||||||
- \[x] Private DNS.
|
- \[x] Private DNS.
|
||||||
- \[x] Network Activity Monitoring.
|
- \[x] Network Activity Monitoring.
|
||||||
|
- \[x] Mobile clients (Android).
|
||||||
|
-
|
||||||
**Coming soon:**
|
**Coming soon:**
|
||||||
- \[ ] Mobile clients.
|
- \[ ] Mobile clients (iOS).
|
||||||
|
|
||||||
### Secure peer-to-peer VPN with SSO and MFA in minutes
|
### Secure peer-to-peer VPN with SSO and MFA in minutes
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
gstatus "google.golang.org/grpc/status"
|
gstatus "google.golang.org/grpc/status"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/cmd"
|
"github.com/netbirdio/netbird/client/cmd"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"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
|
// 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 := a.withBackOff(a.ctx, func() (err error) {
|
||||||
_, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
_, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
||||||
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound {
|
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound {
|
||||||
supportsSSO = false
|
_, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
||||||
err = nil
|
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound {
|
||||||
|
supportsSSO = false
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -183,27 +188,15 @@ func (a *Auth) login(urlOpener URLOpener) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*internal.TokenInfo, error) {
|
func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, error) {
|
||||||
providerConfig, err := internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s, ok := gstatus.FromError(err)
|
return nil, 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hostedClient := internal.NewHostedDeviceFlow(providerConfig.ProviderConfig)
|
flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO())
|
||||||
|
|
||||||
flowInfo, err := hostedClient.RequestDeviceCode(context.TODO())
|
|
||||||
if err != nil {
|
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)
|
go urlOpener.Open(flowInfo.VerificationURIComplete)
|
||||||
@@ -211,7 +204,7 @@ func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*internal.TokenInfo,
|
|||||||
waitTimeout := time.Duration(flowInfo.ExpiresIn)
|
waitTimeout := time.Duration(flowInfo.ExpiresIn)
|
||||||
waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout*time.Second)
|
waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
tokenInfo, err := hostedClient.WaitToken(waitCTX, flowInfo)
|
tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("waiting for browser login failed: %v", err)
|
return nil, fmt.Errorf("waiting for browser login failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -163,31 +164,15 @@ func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *internal.C
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *internal.Config) (*internal.TokenInfo, error) {
|
func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *internal.Config) (*auth.TokenInfo, error) {
|
||||||
providerConfig, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
|
oAuthFlow, err := auth.NewOAuthFlow(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s, ok := gstatus.FromError(err)
|
return nil, 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hostedClient := internal.NewHostedDeviceFlow(providerConfig.ProviderConfig)
|
flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO())
|
||||||
|
|
||||||
flowInfo, err := hostedClient.RequestDeviceCode(context.TODO())
|
|
||||||
if err != nil {
|
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)
|
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)
|
waitCTX, c := context.WithTimeout(context.TODO(), waitTimeout*time.Second)
|
||||||
defer c()
|
defer c()
|
||||||
|
|
||||||
tokenInfo, err := hostedClient.WaitToken(waitCTX, flowInfo)
|
tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("waiting for browser login failed: %v", err)
|
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) {
|
func openURL(cmd *cobra.Command, verificationURIComplete, userCode string) {
|
||||||
var codeMsg string
|
var codeMsg string
|
||||||
if !strings.Contains(verificationURIComplete, userCode) {
|
if userCode != "" {
|
||||||
codeMsg = fmt.Sprintf("and enter the code %s to authenticate.", userCode)
|
if !strings.Contains(verificationURIComplete, userCode) {
|
||||||
|
codeMsg = fmt.Sprintf("and enter the code %s to authenticate.", userCode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := open.Run(verificationURIComplete)
|
err := open.Run(verificationURIComplete)
|
||||||
|
|||||||
202
client/internal/auth/device_flow.go
Normal file
202
client/internal/auth/device_flow.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
package internal
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockHTTPClient struct {
|
type mockHTTPClient struct {
|
||||||
@@ -53,7 +53,7 @@ func TestHosted_RequestDeviceCode(t *testing.T) {
|
|||||||
testingErrFunc require.ErrorAssertionFunc
|
testingErrFunc require.ErrorAssertionFunc
|
||||||
expectedErrorMSG string
|
expectedErrorMSG string
|
||||||
testingFunc require.ComparisonAssertionFunc
|
testingFunc require.ComparisonAssertionFunc
|
||||||
expectedOut DeviceAuthInfo
|
expectedOut AuthFlowInfo
|
||||||
expectedMSG string
|
expectedMSG string
|
||||||
expectPayload string
|
expectPayload string
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ func TestHosted_RequestDeviceCode(t *testing.T) {
|
|||||||
testingFunc: require.EqualValues,
|
testingFunc: require.EqualValues,
|
||||||
expectPayload: expectPayload,
|
expectPayload: expectPayload,
|
||||||
}
|
}
|
||||||
testCase4Out := DeviceAuthInfo{ExpiresIn: 10}
|
testCase4Out := AuthFlowInfo{ExpiresIn: 10}
|
||||||
testCase4 := test{
|
testCase4 := test{
|
||||||
name: "Got Device Code",
|
name: "Got Device Code",
|
||||||
inputResBody: fmt.Sprintf("{\"expires_in\":%d}", testCase4Out.ExpiresIn),
|
inputResBody: fmt.Sprintf("{\"expires_in\":%d}", testCase4Out.ExpiresIn),
|
||||||
@@ -113,8 +113,8 @@ func TestHosted_RequestDeviceCode(t *testing.T) {
|
|||||||
err: testCase.inputReqError,
|
err: testCase.inputReqError,
|
||||||
}
|
}
|
||||||
|
|
||||||
hosted := Hosted{
|
deviceFlow := &DeviceAuthorizationFlow{
|
||||||
providerConfig: ProviderConfig{
|
providerConfig: internal.DeviceAuthProviderConfig{
|
||||||
Audience: expectedAudience,
|
Audience: expectedAudience,
|
||||||
ClientID: expectedClientID,
|
ClientID: expectedClientID,
|
||||||
Scope: expectedScope,
|
Scope: expectedScope,
|
||||||
@@ -125,7 +125,7 @@ func TestHosted_RequestDeviceCode(t *testing.T) {
|
|||||||
HTTPClient: &httpClient,
|
HTTPClient: &httpClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
authInfo, err := hosted.RequestDeviceCode(context.TODO())
|
authInfo, err := deviceFlow.RequestAuthInfo(context.TODO())
|
||||||
testCase.testingErrFunc(t, err, testCase.expectedErrorMSG)
|
testCase.testingErrFunc(t, err, testCase.expectedErrorMSG)
|
||||||
|
|
||||||
require.EqualValues(t, expectPayload, httpClient.reqBody, "payload should match")
|
require.EqualValues(t, expectPayload, httpClient.reqBody, "payload should match")
|
||||||
@@ -145,7 +145,7 @@ func TestHosted_WaitToken(t *testing.T) {
|
|||||||
inputMaxReqs int
|
inputMaxReqs int
|
||||||
inputCountResBody string
|
inputCountResBody string
|
||||||
inputTimeout time.Duration
|
inputTimeout time.Duration
|
||||||
inputInfo DeviceAuthInfo
|
inputInfo AuthFlowInfo
|
||||||
inputAudience string
|
inputAudience string
|
||||||
testingErrFunc require.ErrorAssertionFunc
|
testingErrFunc require.ErrorAssertionFunc
|
||||||
expectedErrorMSG string
|
expectedErrorMSG string
|
||||||
@@ -155,7 +155,7 @@ func TestHosted_WaitToken(t *testing.T) {
|
|||||||
expectPayload string
|
expectPayload string
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultInfo := DeviceAuthInfo{
|
defaultInfo := AuthFlowInfo{
|
||||||
DeviceCode: "test",
|
DeviceCode: "test",
|
||||||
ExpiresIn: 10,
|
ExpiresIn: 10,
|
||||||
Interval: 1,
|
Interval: 1,
|
||||||
@@ -278,8 +278,8 @@ func TestHosted_WaitToken(t *testing.T) {
|
|||||||
countResBody: testCase.inputCountResBody,
|
countResBody: testCase.inputCountResBody,
|
||||||
}
|
}
|
||||||
|
|
||||||
hosted := Hosted{
|
deviceFlow := DeviceAuthorizationFlow{
|
||||||
providerConfig: ProviderConfig{
|
providerConfig: internal.DeviceAuthProviderConfig{
|
||||||
Audience: testCase.inputAudience,
|
Audience: testCase.inputAudience,
|
||||||
ClientID: clientID,
|
ClientID: clientID,
|
||||||
TokenEndpoint: "test.hosted.com/token",
|
TokenEndpoint: "test.hosted.com/token",
|
||||||
@@ -287,11 +287,12 @@ func TestHosted_WaitToken(t *testing.T) {
|
|||||||
Scope: "openid",
|
Scope: "openid",
|
||||||
UseIDToken: false,
|
UseIDToken: false,
|
||||||
},
|
},
|
||||||
HTTPClient: &httpClient}
|
HTTPClient: &httpClient,
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.TODO(), testCase.inputTimeout)
|
ctx, cancel := context.WithTimeout(context.TODO(), testCase.inputTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
tokenInfo, err := hosted.WaitToken(ctx, testCase.inputInfo)
|
tokenInfo, err := deviceFlow.WaitToken(ctx, testCase.inputInfo)
|
||||||
testCase.testingErrFunc(t, err, testCase.expectedErrorMSG)
|
testCase.testingErrFunc(t, err, testCase.expectedErrorMSG)
|
||||||
|
|
||||||
require.EqualValues(t, testCase.expectPayload, httpClient.reqBody, "payload should match")
|
require.EqualValues(t, testCase.expectPayload, httpClient.reqBody, "payload should match")
|
||||||
90
client/internal/auth/oauth.go
Normal file
90
client/internal/auth/oauth.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
238
client/internal/auth/pkce_flow.go
Normal file
238
client/internal/auth/pkce_flow.go
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
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) {
|
||||||
|
tokenValidatorFunc := func() (*oauth2.Token, error) {
|
||||||
|
query := req.URL.Query()
|
||||||
|
|
||||||
|
state := query.Get(queryState)
|
||||||
|
// Prevent timing attacks on state
|
||||||
|
if subtle.ConstantTimeCompare([]byte(p.state), []byte(state)) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid state")
|
||||||
|
}
|
||||||
|
|
||||||
|
code := query.Get(queryCode)
|
||||||
|
if code == "" {
|
||||||
|
return nil, fmt.Errorf("missing code")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.oAuthConfig.Exchange(
|
||||||
|
req.Context(),
|
||||||
|
code,
|
||||||
|
oauth2.SetAuthURLParam("code_verifier", p.codeVerifier),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := tokenValidatorFunc()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- fmt.Errorf("PKCE authorization flow failed: %v", err)
|
||||||
|
renderPKCEFlowTmpl(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenChan <- token
|
||||||
|
renderPKCEFlowTmpl(w, nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderPKCEFlowTmpl(w http.ResponseWriter, authError error) {
|
||||||
|
tmpl, err := template.New("pkce-auth-flow").Parse(templates.PKCEAuthMsgTmpl)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[string]string)
|
||||||
|
if authError != nil {
|
||||||
|
data["Error"] = authError.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tmpl.Execute(w, data); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
62
client/internal/auth/util.go
Normal file
62
client/internal/auth/util.go
Normal file
@@ -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")
|
||||||
|
}
|
||||||
@@ -16,11 +16,11 @@ import (
|
|||||||
// DeviceAuthorizationFlow represents Device Authorization Flow information
|
// DeviceAuthorizationFlow represents Device Authorization Flow information
|
||||||
type DeviceAuthorizationFlow struct {
|
type DeviceAuthorizationFlow struct {
|
||||||
Provider string
|
Provider string
|
||||||
ProviderConfig ProviderConfig
|
ProviderConfig DeviceAuthProviderConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProviderConfig has all attributes needed to initiate a device authorization flow
|
// DeviceAuthProviderConfig has all attributes needed to initiate a device authorization flow
|
||||||
type ProviderConfig struct {
|
type DeviceAuthProviderConfig struct {
|
||||||
// ClientID An IDP application client id
|
// ClientID An IDP application client id
|
||||||
ClientID string
|
ClientID string
|
||||||
// ClientSecret An IDP application client secret
|
// ClientSecret An IDP application client secret
|
||||||
@@ -88,7 +88,7 @@ func GetDeviceAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmU
|
|||||||
deviceAuthorizationFlow := DeviceAuthorizationFlow{
|
deviceAuthorizationFlow := DeviceAuthorizationFlow{
|
||||||
Provider: protoDeviceAuthorizationFlow.Provider.String(),
|
Provider: protoDeviceAuthorizationFlow.Provider.String(),
|
||||||
|
|
||||||
ProviderConfig: ProviderConfig{
|
ProviderConfig: DeviceAuthProviderConfig{
|
||||||
Audience: protoDeviceAuthorizationFlow.GetProviderConfig().GetAudience(),
|
Audience: protoDeviceAuthorizationFlow.GetProviderConfig().GetAudience(),
|
||||||
ClientID: protoDeviceAuthorizationFlow.GetProviderConfig().GetClientID(),
|
ClientID: protoDeviceAuthorizationFlow.GetProviderConfig().GetClientID(),
|
||||||
ClientSecret: protoDeviceAuthorizationFlow.GetProviderConfig().GetClientSecret(),
|
ClientSecret: protoDeviceAuthorizationFlow.GetProviderConfig().GetClientSecret(),
|
||||||
@@ -105,7 +105,7 @@ func GetDeviceAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmU
|
|||||||
deviceAuthorizationFlow.ProviderConfig.Scope = "openid"
|
deviceAuthorizationFlow.ProviderConfig.Scope = "openid"
|
||||||
}
|
}
|
||||||
|
|
||||||
err = isProviderConfigValid(deviceAuthorizationFlow.ProviderConfig)
|
err = isDeviceAuthProviderConfigValid(deviceAuthorizationFlow.ProviderConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DeviceAuthorizationFlow{}, err
|
return DeviceAuthorizationFlow{}, err
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ func GetDeviceAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmU
|
|||||||
return deviceAuthorizationFlow, nil
|
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"
|
errorMSGFormat := "invalid provider configuration received from management: %s value is empty. Contact your NetBird administrator"
|
||||||
if config.Audience == "" {
|
if config.Audience == "" {
|
||||||
return fmt.Errorf(errorMSGFormat, "Audience")
|
return fmt.Errorf(errorMSGFormat, "Audience")
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/acl"
|
"github.com/netbirdio/netbird/client/internal/acl"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/proxy"
|
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/wgproxy"
|
||||||
nbssh "github.com/netbirdio/netbird/client/ssh"
|
nbssh "github.com/netbirdio/netbird/client/ssh"
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
@@ -101,7 +101,8 @@ type Engine struct {
|
|||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
wgInterface *iface.WGIface
|
wgInterface *iface.WGIface
|
||||||
|
wgProxyFactory *wgproxy.Factory
|
||||||
|
|
||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *bind.UniversalUDPMuxDefault
|
||||||
udpMuxConn io.Closer
|
udpMuxConn io.Closer
|
||||||
@@ -132,6 +133,7 @@ func NewEngine(
|
|||||||
signalClient signal.Client, mgmClient mgm.Client,
|
signalClient signal.Client, mgmClient mgm.Client,
|
||||||
config *EngineConfig, mobileDep MobileDependency, statusRecorder *peer.Status,
|
config *EngineConfig, mobileDep MobileDependency, statusRecorder *peer.Status,
|
||||||
) *Engine {
|
) *Engine {
|
||||||
|
|
||||||
return &Engine{
|
return &Engine{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
@@ -146,6 +148,7 @@ func NewEngine(
|
|||||||
networkSerial: 0,
|
networkSerial: 0,
|
||||||
sshServerFunc: nbssh.DefaultSSHServer,
|
sshServerFunc: nbssh.DefaultSSHServer,
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
|
wgProxyFactory: wgproxy.NewFactory(config.WgPort),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +285,7 @@ func (e *Engine) modifyPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
|
|||||||
for _, p := range peersUpdate {
|
for _, p := range peersUpdate {
|
||||||
peerPubKey := p.GetWgPubKey()
|
peerPubKey := p.GetWgPubKey()
|
||||||
if peerConn, ok := e.peerConns[peerPubKey]; ok {
|
if peerConn, ok := e.peerConns[peerPubKey]; ok {
|
||||||
if peerConn.GetConf().ProxyConfig.AllowedIps != strings.Join(p.AllowedIps, ",") {
|
if peerConn.WgConfig().AllowedIps != strings.Join(p.AllowedIps, ",") {
|
||||||
modified = append(modified, p)
|
modified = append(modified, p)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -795,9 +798,7 @@ func (e *Engine) connWorker(conn *peer.Conn, peerKey string) {
|
|||||||
|
|
||||||
// we might have received new STUN and TURN servers meanwhile, so update them
|
// we might have received new STUN and TURN servers meanwhile, so update them
|
||||||
e.syncMsgMux.Lock()
|
e.syncMsgMux.Lock()
|
||||||
conf := conn.GetConf()
|
conn.UpdateStunTurn(append(e.STUNs, e.TURNs...))
|
||||||
conf.StunTurn = append(e.STUNs, e.TURNs...)
|
|
||||||
conn.UpdateConf(conf)
|
|
||||||
e.syncMsgMux.Unlock()
|
e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
err := conn.Open()
|
err := conn.Open()
|
||||||
@@ -826,9 +827,9 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, e
|
|||||||
stunTurn = append(stunTurn, e.STUNs...)
|
stunTurn = append(stunTurn, e.STUNs...)
|
||||||
stunTurn = append(stunTurn, e.TURNs...)
|
stunTurn = append(stunTurn, e.TURNs...)
|
||||||
|
|
||||||
proxyConfig := proxy.Config{
|
wgConfig := peer.WgConfig{
|
||||||
RemoteKey: pubKey,
|
RemoteKey: pubKey,
|
||||||
WgListenAddr: fmt.Sprintf("127.0.0.1:%d", e.config.WgPort),
|
WgListenPort: e.config.WgPort,
|
||||||
WgInterface: e.wgInterface,
|
WgInterface: e.wgInterface,
|
||||||
AllowedIps: allowedIPs,
|
AllowedIps: allowedIPs,
|
||||||
PreSharedKey: e.config.PreSharedKey,
|
PreSharedKey: e.config.PreSharedKey,
|
||||||
@@ -845,13 +846,13 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, e
|
|||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
UDPMux: e.udpMux.UDPMuxDefault,
|
UDPMux: e.udpMux.UDPMuxDefault,
|
||||||
UDPMuxSrflx: e.udpMux,
|
UDPMuxSrflx: e.udpMux,
|
||||||
ProxyConfig: proxyConfig,
|
WgConfig: wgConfig,
|
||||||
LocalWgPort: e.config.WgPort,
|
LocalWgPort: e.config.WgPort,
|
||||||
NATExternalIPs: e.parseNATExternalIPMappings(),
|
NATExternalIPs: e.parseNATExternalIPMappings(),
|
||||||
UserspaceBind: e.wgInterface.IsUserspaceBind(),
|
UserspaceBind: e.wgInterface.IsUserspaceBind(),
|
||||||
}
|
}
|
||||||
|
|
||||||
peerConn, err := peer.NewConn(config, e.statusRecorder, e.mobileDep.TunAdapter, e.mobileDep.IFaceDiscover)
|
peerConn, err := peer.NewConn(config, e.statusRecorder, e.wgProxyFactory, e.mobileDep.TunAdapter, e.mobileDep.IFaceDiscover)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1008,6 +1009,10 @@ func (e *Engine) parseNATExternalIPMappings() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) close() {
|
func (e *Engine) close() {
|
||||||
|
if err := e.wgProxyFactory.Free(); err != nil {
|
||||||
|
log.Errorf("failed closing ebpf proxy: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
log.Debugf("removing Netbird interface %s", e.config.WgIfaceName)
|
log.Debugf("removing Netbird interface %s", e.config.WgIfaceName)
|
||||||
if e.wgInterface != nil {
|
if e.wgInterface != nil {
|
||||||
if err := e.wgInterface.Close(); err != nil {
|
if err := e.wgInterface.Close(); err != nil {
|
||||||
|
|||||||
@@ -367,9 +367,9 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
|||||||
t.Errorf("expecting Engine.peerConns to contain peer %s", p)
|
t.Errorf("expecting Engine.peerConns to contain peer %s", p)
|
||||||
}
|
}
|
||||||
expectedAllowedIPs := strings.Join(p.AllowedIps, ",")
|
expectedAllowedIPs := strings.Join(p.AllowedIps, ",")
|
||||||
if conn.GetConf().ProxyConfig.AllowedIps != expectedAllowedIPs {
|
if conn.WgConfig().AllowedIps != expectedAllowedIPs {
|
||||||
t.Errorf("expecting peer %s to have AllowedIPs= %s, got %s", p.GetWgPubKey(),
|
t.Errorf("expecting peer %s to have AllowedIPs= %s, got %s", p.GetWgPubKey(),
|
||||||
expectedAllowedIPs, conn.GetConf().ProxyConfig.AllowedIps)
|
expectedAllowedIPs, conn.WgConfig().AllowedIps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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")
|
|
||||||
}
|
|
||||||
@@ -10,9 +10,10 @@ import (
|
|||||||
|
|
||||||
"github.com/pion/ice/v2"
|
"github.com/pion/ice/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/proxy"
|
|
||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/wgproxy"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"github.com/netbirdio/netbird/iface/bind"
|
"github.com/netbirdio/netbird/iface/bind"
|
||||||
signal "github.com/netbirdio/netbird/signal/client"
|
signal "github.com/netbirdio/netbird/signal/client"
|
||||||
@@ -23,8 +24,18 @@ import (
|
|||||||
const (
|
const (
|
||||||
iceKeepAliveDefault = 4 * time.Second
|
iceKeepAliveDefault = 4 * time.Second
|
||||||
iceDisconnectedTimeoutDefault = 6 * time.Second
|
iceDisconnectedTimeoutDefault = 6 * time.Second
|
||||||
|
|
||||||
|
defaultWgKeepAlive = 25 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type WgConfig struct {
|
||||||
|
WgListenPort int
|
||||||
|
RemoteKey string
|
||||||
|
WgInterface *iface.WGIface
|
||||||
|
AllowedIps string
|
||||||
|
PreSharedKey *wgtypes.Key
|
||||||
|
}
|
||||||
|
|
||||||
// ConnConfig is a peer Connection configuration
|
// ConnConfig is a peer Connection configuration
|
||||||
type ConnConfig struct {
|
type ConnConfig struct {
|
||||||
|
|
||||||
@@ -43,7 +54,7 @@ type ConnConfig struct {
|
|||||||
|
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
|
||||||
ProxyConfig proxy.Config
|
WgConfig WgConfig
|
||||||
|
|
||||||
UDPMux ice.UDPMux
|
UDPMux ice.UDPMux
|
||||||
UDPMuxSrflx ice.UniversalUDPMux
|
UDPMuxSrflx ice.UniversalUDPMux
|
||||||
@@ -98,7 +109,9 @@ type Conn struct {
|
|||||||
|
|
||||||
statusRecorder *Status
|
statusRecorder *Status
|
||||||
|
|
||||||
proxy proxy.Proxy
|
wgProxyFactory *wgproxy.Factory
|
||||||
|
wgProxy wgproxy.Proxy
|
||||||
|
|
||||||
remoteModeCh chan ModeMessage
|
remoteModeCh chan ModeMessage
|
||||||
meta meta
|
meta meta
|
||||||
|
|
||||||
@@ -122,14 +135,19 @@ func (conn *Conn) GetConf() ConnConfig {
|
|||||||
return conn.config
|
return conn.config
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateConf updates the connection config
|
// WgConfig returns the WireGuard config
|
||||||
func (conn *Conn) UpdateConf(conf ConnConfig) {
|
func (conn *Conn) WgConfig() WgConfig {
|
||||||
conn.config = conf
|
return conn.config.WgConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStunTurn update the turn and stun addresses
|
||||||
|
func (conn *Conn) UpdateStunTurn(turnStun []*ice.URL) {
|
||||||
|
conn.config.StunTurn = turnStun
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConn creates a new not opened Conn to the remote peer.
|
// NewConn creates a new not opened Conn to the remote peer.
|
||||||
// To establish a connection run Conn.Open
|
// To establish a connection run Conn.Open
|
||||||
func NewConn(config ConnConfig, statusRecorder *Status, adapter iface.TunAdapter, iFaceDiscover stdnet.ExternalIFaceDiscover) (*Conn, error) {
|
func NewConn(config ConnConfig, statusRecorder *Status, wgProxyFactory *wgproxy.Factory, adapter iface.TunAdapter, iFaceDiscover stdnet.ExternalIFaceDiscover) (*Conn, error) {
|
||||||
return &Conn{
|
return &Conn{
|
||||||
config: config,
|
config: config,
|
||||||
mu: sync.Mutex{},
|
mu: sync.Mutex{},
|
||||||
@@ -139,6 +157,7 @@ func NewConn(config ConnConfig, statusRecorder *Status, adapter iface.TunAdapter
|
|||||||
remoteAnswerCh: make(chan OfferAnswer),
|
remoteAnswerCh: make(chan OfferAnswer),
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
remoteModeCh: make(chan ModeMessage, 1),
|
remoteModeCh: make(chan ModeMessage, 1),
|
||||||
|
wgProxyFactory: wgProxyFactory,
|
||||||
adapter: adapter,
|
adapter: adapter,
|
||||||
iFaceDiscover: iFaceDiscover,
|
iFaceDiscover: iFaceDiscover,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -215,12 +234,12 @@ func (conn *Conn) candidateTypes() []ice.CandidateType {
|
|||||||
func (conn *Conn) Open() error {
|
func (conn *Conn) Open() error {
|
||||||
log.Debugf("trying to connect to peer %s", conn.config.Key)
|
log.Debugf("trying to connect to peer %s", conn.config.Key)
|
||||||
|
|
||||||
peerState := State{PubKey: conn.config.Key}
|
peerState := State{
|
||||||
|
PubKey: conn.config.Key,
|
||||||
peerState.IP = strings.Split(conn.config.ProxyConfig.AllowedIps, "/")[0]
|
IP: strings.Split(conn.config.WgConfig.AllowedIps, "/")[0],
|
||||||
peerState.ConnStatusUpdate = time.Now()
|
ConnStatusUpdate: time.Now(),
|
||||||
peerState.ConnStatus = conn.status
|
ConnStatus: conn.status,
|
||||||
|
}
|
||||||
err := conn.statusRecorder.UpdatePeerState(peerState)
|
err := conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err)
|
log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err)
|
||||||
@@ -275,10 +294,11 @@ func (conn *Conn) Open() error {
|
|||||||
defer conn.notifyDisconnected()
|
defer conn.notifyDisconnected()
|
||||||
conn.mu.Unlock()
|
conn.mu.Unlock()
|
||||||
|
|
||||||
peerState = State{PubKey: conn.config.Key}
|
peerState = State{
|
||||||
|
PubKey: conn.config.Key,
|
||||||
peerState.ConnStatus = conn.status
|
ConnStatus: conn.status,
|
||||||
peerState.ConnStatusUpdate = time.Now()
|
ConnStatusUpdate: time.Now(),
|
||||||
|
}
|
||||||
err = conn.statusRecorder.UpdatePeerState(peerState)
|
err = conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err)
|
log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err)
|
||||||
@@ -309,19 +329,12 @@ func (conn *Conn) Open() error {
|
|||||||
remoteWgPort = remoteOfferAnswer.WgListenPort
|
remoteWgPort = remoteOfferAnswer.WgListenPort
|
||||||
}
|
}
|
||||||
// the ice connection has been established successfully so we are ready to start the proxy
|
// the ice connection has been established successfully so we are ready to start the proxy
|
||||||
err = conn.startProxy(remoteConn, remoteWgPort)
|
remoteAddr, err := conn.configureConnection(remoteConn, remoteWgPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn.proxy.Type() == proxy.TypeDirectNoProxy {
|
log.Infof("connected to peer %s, endpoint address: %s", conn.config.Key, remoteAddr.String())
|
||||||
host, _, _ := net.SplitHostPort(remoteConn.LocalAddr().String())
|
|
||||||
rhost, _, _ := net.SplitHostPort(remoteConn.RemoteAddr().String())
|
|
||||||
// direct Wireguard connection
|
|
||||||
log.Infof("directly connected to peer %s [laddr <-> raddr] [%s:%d <-> %s:%d]", conn.config.Key, host, conn.config.LocalWgPort, rhost, remoteWgPort)
|
|
||||||
} else {
|
|
||||||
log.Infof("connected to peer %s [laddr <-> raddr] [%s <-> %s]", conn.config.Key, remoteConn.LocalAddr().String(), remoteConn.RemoteAddr().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait until connection disconnected or has been closed externally (upper layer, e.g. engine)
|
// wait until connection disconnected or has been closed externally (upper layer, e.g. engine)
|
||||||
select {
|
select {
|
||||||
@@ -338,54 +351,60 @@ func isRelayCandidate(candidate ice.Candidate) bool {
|
|||||||
return candidate.Type() == ice.CandidateTypeRelay
|
return candidate.Type() == ice.CandidateTypeRelay
|
||||||
}
|
}
|
||||||
|
|
||||||
// startProxy starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected
|
// configureConnection starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected
|
||||||
func (conn *Conn) startProxy(remoteConn net.Conn, remoteWgPort int) error {
|
func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int) (net.Addr, error) {
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
defer conn.mu.Unlock()
|
defer conn.mu.Unlock()
|
||||||
|
|
||||||
var pair *ice.CandidatePair
|
|
||||||
pair, err := conn.agent.GetSelectedCandidatePair()
|
pair, err := conn.agent.GetSelectedCandidatePair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
peerState := State{PubKey: conn.config.Key}
|
var endpoint net.Addr
|
||||||
p := conn.getProxy(pair, remoteWgPort)
|
if isRelayCandidate(pair.Local) {
|
||||||
conn.proxy = p
|
log.Debugf("setup relay connection")
|
||||||
err = p.Start(remoteConn)
|
conn.wgProxy = conn.wgProxyFactory.GetProxy()
|
||||||
|
endpoint, err = conn.wgProxy.AddTurnConn(remoteConn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// To support old version's with direct mode we attempt to punch an additional role with the remote wireguard port
|
||||||
|
go conn.punchRemoteWGPort(pair, remoteWgPort)
|
||||||
|
endpoint = remoteConn.RemoteAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
endpointUdpAddr, _ := net.ResolveUDPAddr(endpoint.Network(), endpoint.String())
|
||||||
|
|
||||||
|
err = conn.config.WgConfig.WgInterface.UpdatePeer(conn.config.WgConfig.RemoteKey, conn.config.WgConfig.AllowedIps, defaultWgKeepAlive, endpointUdpAddr, conn.config.WgConfig.PreSharedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
if conn.wgProxy != nil {
|
||||||
|
_ = conn.wgProxy.CloseConn()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.status = StatusConnected
|
conn.status = StatusConnected
|
||||||
|
|
||||||
peerState.ConnStatus = conn.status
|
peerState := State{
|
||||||
peerState.ConnStatusUpdate = time.Now()
|
PubKey: conn.config.Key,
|
||||||
peerState.LocalIceCandidateType = pair.Local.Type().String()
|
ConnStatus: conn.status,
|
||||||
peerState.RemoteIceCandidateType = pair.Remote.Type().String()
|
ConnStatusUpdate: time.Now(),
|
||||||
|
LocalIceCandidateType: pair.Local.Type().String(),
|
||||||
|
RemoteIceCandidateType: pair.Remote.Type().String(),
|
||||||
|
Direct: !isRelayCandidate(pair.Local),
|
||||||
|
}
|
||||||
if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay {
|
if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay {
|
||||||
peerState.Relayed = true
|
peerState.Relayed = true
|
||||||
}
|
}
|
||||||
peerState.Direct = p.Type() == proxy.TypeDirectNoProxy || p.Type() == proxy.TypeNoProxy
|
|
||||||
|
|
||||||
err = conn.statusRecorder.UpdatePeerState(peerState)
|
err = conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("unable to save peer's state, got error: %v", err)
|
log.Warnf("unable to save peer's state, got error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return endpoint, nil
|
||||||
}
|
|
||||||
|
|
||||||
// todo rename this method and the proxy package to something more appropriate
|
|
||||||
func (conn *Conn) getProxy(pair *ice.CandidatePair, remoteWgPort int) proxy.Proxy {
|
|
||||||
if isRelayCandidate(pair.Local) {
|
|
||||||
return proxy.NewWireGuardProxy(conn.config.ProxyConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// To support old version's with direct mode we attempt to punch an additional role with the remote wireguard port
|
|
||||||
go conn.punchRemoteWGPort(pair, remoteWgPort)
|
|
||||||
|
|
||||||
return proxy.NewNoProxy(conn.config.ProxyConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int) {
|
func (conn *Conn) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int) {
|
||||||
@@ -414,22 +433,22 @@ func (conn *Conn) cleanup() error {
|
|||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
defer conn.mu.Unlock()
|
defer conn.mu.Unlock()
|
||||||
|
|
||||||
|
var err1, err2, err3 error
|
||||||
if conn.agent != nil {
|
if conn.agent != nil {
|
||||||
err := conn.agent.Close()
|
err1 = conn.agent.Close()
|
||||||
if err != nil {
|
if err1 == nil {
|
||||||
return err
|
conn.agent = nil
|
||||||
}
|
}
|
||||||
conn.agent = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn.proxy != nil {
|
if conn.wgProxy != nil {
|
||||||
err := conn.proxy.Close()
|
err2 = conn.wgProxy.CloseConn()
|
||||||
if err != nil {
|
conn.wgProxy = nil
|
||||||
return err
|
|
||||||
}
|
|
||||||
conn.proxy = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: is it problem if we try to remove a peer what is never existed?
|
||||||
|
err3 = conn.config.WgConfig.WgInterface.RemovePeer(conn.config.WgConfig.RemoteKey)
|
||||||
|
|
||||||
if conn.notifyDisconnected != nil {
|
if conn.notifyDisconnected != nil {
|
||||||
conn.notifyDisconnected()
|
conn.notifyDisconnected()
|
||||||
conn.notifyDisconnected = nil
|
conn.notifyDisconnected = nil
|
||||||
@@ -437,10 +456,11 @@ func (conn *Conn) cleanup() error {
|
|||||||
|
|
||||||
conn.status = StatusDisconnected
|
conn.status = StatusDisconnected
|
||||||
|
|
||||||
peerState := State{PubKey: conn.config.Key}
|
peerState := State{
|
||||||
peerState.ConnStatus = conn.status
|
PubKey: conn.config.Key,
|
||||||
peerState.ConnStatusUpdate = time.Now()
|
ConnStatus: conn.status,
|
||||||
|
ConnStatusUpdate: time.Now(),
|
||||||
|
}
|
||||||
err := conn.statusRecorder.UpdatePeerState(peerState)
|
err := conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// pretty common error because by that time Engine can already remove the peer and status won't be available.
|
// pretty common error because by that time Engine can already remove the peer and status won't be available.
|
||||||
@@ -449,8 +469,13 @@ func (conn *Conn) cleanup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("cleaned up connection to peer %s", conn.config.Key)
|
log.Debugf("cleaned up connection to peer %s", conn.config.Key)
|
||||||
|
if err1 != nil {
|
||||||
return nil
|
return err1
|
||||||
|
}
|
||||||
|
if err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
return err3
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSignalOffer sets a handler function to be triggered by Conn when a new connection offer has to be signalled to the remote peer
|
// SetSignalOffer sets a handler function to be triggered by Conn when a new connection offer has to be signalled to the remote peer
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
|
||||||
|
|
||||||
"github.com/magiconair/properties/assert"
|
"github.com/magiconair/properties/assert"
|
||||||
"github.com/pion/ice/v2"
|
"github.com/pion/ice/v2"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/proxy"
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/wgproxy"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +19,6 @@ var connConf = ConnConfig{
|
|||||||
StunTurn: []*ice.URL{},
|
StunTurn: []*ice.URL{},
|
||||||
InterfaceBlackList: nil,
|
InterfaceBlackList: nil,
|
||||||
Timeout: time.Second,
|
Timeout: time.Second,
|
||||||
ProxyConfig: proxy.Config{},
|
|
||||||
LocalWgPort: 51820,
|
LocalWgPort: 51820,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +35,11 @@ func TestNewConn_interfaceFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConn_GetKey(t *testing.T) {
|
func TestConn_GetKey(t *testing.T) {
|
||||||
conn, err := NewConn(connConf, nil, nil, nil)
|
wgProxyFactory := wgproxy.NewFactory(connConf.LocalWgPort)
|
||||||
|
defer func() {
|
||||||
|
_ = wgProxyFactory.Free()
|
||||||
|
}()
|
||||||
|
conn, err := NewConn(connConf, nil, wgProxyFactory, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -48,8 +50,11 @@ func TestConn_GetKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConn_OnRemoteOffer(t *testing.T) {
|
func TestConn_OnRemoteOffer(t *testing.T) {
|
||||||
|
wgProxyFactory := wgproxy.NewFactory(connConf.LocalWgPort)
|
||||||
conn, err := NewConn(connConf, NewRecorder("https://mgm"), nil, nil)
|
defer func() {
|
||||||
|
_ = wgProxyFactory.Free()
|
||||||
|
}()
|
||||||
|
conn, err := NewConn(connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -82,8 +87,11 @@ func TestConn_OnRemoteOffer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConn_OnRemoteAnswer(t *testing.T) {
|
func TestConn_OnRemoteAnswer(t *testing.T) {
|
||||||
|
wgProxyFactory := wgproxy.NewFactory(connConf.LocalWgPort)
|
||||||
conn, err := NewConn(connConf, NewRecorder("https://mgm"), nil, nil)
|
defer func() {
|
||||||
|
_ = wgProxyFactory.Free()
|
||||||
|
}()
|
||||||
|
conn, err := NewConn(connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -115,8 +123,11 @@ func TestConn_OnRemoteAnswer(t *testing.T) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
func TestConn_Status(t *testing.T) {
|
func TestConn_Status(t *testing.T) {
|
||||||
|
wgProxyFactory := wgproxy.NewFactory(connConf.LocalWgPort)
|
||||||
conn, err := NewConn(connConf, NewRecorder("https://mgm"), nil, nil)
|
defer func() {
|
||||||
|
_ = wgProxyFactory.Free()
|
||||||
|
}()
|
||||||
|
conn, err := NewConn(connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -142,8 +153,11 @@ func TestConn_Status(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConn_Close(t *testing.T) {
|
func TestConn_Close(t *testing.T) {
|
||||||
|
wgProxyFactory := wgproxy.NewFactory(connConf.LocalWgPort)
|
||||||
conn, err := NewConn(connConf, NewRecorder("https://mgm"), nil, nil)
|
defer func() {
|
||||||
|
_ = wgProxyFactory.Free()
|
||||||
|
}()
|
||||||
|
conn, err := NewConn(connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
128
client/internal/pkce_auth.go
Normal file
128
client/internal/pkce_auth.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package proxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DummyProxy just sends pings to the RemoteKey peer and reads responses
|
|
||||||
type DummyProxy struct {
|
|
||||||
conn net.Conn
|
|
||||||
remote string
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDummyProxy(remote string) *DummyProxy {
|
|
||||||
p := &DummyProxy{remote: remote}
|
|
||||||
p.ctx, p.cancel = context.WithCancel(context.Background())
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DummyProxy) Close() error {
|
|
||||||
p.cancel()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DummyProxy) Start(remoteConn net.Conn) error {
|
|
||||||
p.conn = remoteConn
|
|
||||||
go func() {
|
|
||||||
buf := make([]byte, 1500)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-p.ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
_, err := p.conn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("error while reading RemoteKey %s proxy %v", p.remote, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//log.Debugf("received %s from %s", string(buf[:n]), p.remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-p.ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
_, err := p.conn.Write([]byte("hello"))
|
|
||||||
//log.Debugf("sent ping to %s", p.remote)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("error while writing to RemoteKey %s proxy %v", p.remote, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DummyProxy) Type() Type {
|
|
||||||
return TypeDummy
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package proxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NoProxy is used just to configure WireGuard without any local proxy in between.
|
|
||||||
// Used when the WireGuard interface is userspace and uses bind.ICEBind
|
|
||||||
type NoProxy struct {
|
|
||||||
config Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNoProxy creates a new NoProxy with a provided config
|
|
||||||
func NewNoProxy(config Config) *NoProxy {
|
|
||||||
return &NoProxy{config: config}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes peer from the WireGuard interface
|
|
||||||
func (p *NoProxy) Close() error {
|
|
||||||
err := p.config.WgInterface.RemovePeer(p.config.RemoteKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start just updates WireGuard peer with the remote address
|
|
||||||
func (p *NoProxy) Start(remoteConn net.Conn) error {
|
|
||||||
|
|
||||||
log.Debugf("using NoProxy to connect to peer %s at %s", p.config.RemoteKey, remoteConn.RemoteAddr().String())
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", remoteConn.RemoteAddr().String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
|
|
||||||
addr, p.config.PreSharedKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NoProxy) Type() Type {
|
|
||||||
return TypeNoProxy
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package proxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/netbirdio/netbird/iface"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const DefaultWgKeepAlive = 25 * time.Second
|
|
||||||
|
|
||||||
type Type string
|
|
||||||
|
|
||||||
const (
|
|
||||||
TypeDirectNoProxy Type = "DirectNoProxy"
|
|
||||||
TypeWireGuard Type = "WireGuard"
|
|
||||||
TypeDummy Type = "Dummy"
|
|
||||||
TypeNoProxy Type = "NoProxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
WgListenAddr string
|
|
||||||
RemoteKey string
|
|
||||||
WgInterface *iface.WGIface
|
|
||||||
AllowedIps string
|
|
||||||
PreSharedKey *wgtypes.Key
|
|
||||||
}
|
|
||||||
|
|
||||||
type Proxy interface {
|
|
||||||
io.Closer
|
|
||||||
// Start creates a local remoteConn and starts proxying data from/to remoteConn
|
|
||||||
Start(remoteConn net.Conn) error
|
|
||||||
Type() Type
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
package proxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WireGuardProxy proxies
|
|
||||||
type WireGuardProxy struct {
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
|
|
||||||
config Config
|
|
||||||
|
|
||||||
remoteConn net.Conn
|
|
||||||
localConn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWireGuardProxy(config Config) *WireGuardProxy {
|
|
||||||
p := &WireGuardProxy{config: config}
|
|
||||||
p.ctx, p.cancel = context.WithCancel(context.Background())
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *WireGuardProxy) updateEndpoint() error {
|
|
||||||
udpAddr, err := net.ResolveUDPAddr(p.localConn.LocalAddr().Network(), p.localConn.LocalAddr().String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// add local proxy connection as a Wireguard peer
|
|
||||||
err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
|
|
||||||
udpAddr, p.config.PreSharedKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *WireGuardProxy) Start(remoteConn net.Conn) error {
|
|
||||||
p.remoteConn = remoteConn
|
|
||||||
|
|
||||||
var err error
|
|
||||||
p.localConn, err = net.Dial("udp", p.config.WgListenAddr)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed dialing to local Wireguard port %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = p.updateEndpoint()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("error while updating Wireguard peer endpoint [%s] %v", p.config.RemoteKey, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
go p.proxyToRemote()
|
|
||||||
go p.proxyToLocal()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *WireGuardProxy) Close() error {
|
|
||||||
p.cancel()
|
|
||||||
if c := p.localConn; c != nil {
|
|
||||||
err := p.localConn.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := p.config.WgInterface.RemovePeer(p.config.RemoteKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// proxyToRemote proxies everything from Wireguard to the RemoteKey peer
|
|
||||||
// blocks
|
|
||||||
func (p *WireGuardProxy) proxyToRemote() {
|
|
||||||
|
|
||||||
buf := make([]byte, 1500)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-p.ctx.Done():
|
|
||||||
log.Debugf("stopped proxying to remote peer %s due to closed connection", p.config.RemoteKey)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
n, err := p.localConn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = p.remoteConn.Write(buf[:n])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// proxyToLocal proxies everything from the RemoteKey peer to local Wireguard
|
|
||||||
// blocks
|
|
||||||
func (p *WireGuardProxy) proxyToLocal() {
|
|
||||||
|
|
||||||
buf := make([]byte, 1500)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-p.ctx.Done():
|
|
||||||
log.Debugf("stopped proxying from remote peer %s due to closed connection", p.config.RemoteKey)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
n, err := p.remoteConn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = p.localConn.Write(buf[:n])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *WireGuardProxy) Type() Type {
|
|
||||||
return TypeWireGuard
|
|
||||||
}
|
|
||||||
8
client/internal/templates/embed.go
Normal file
8
client/internal/templates/embed.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed pkce-auth-msg.html
|
||||||
|
var PKCEAuthMsgTmpl string
|
||||||
87
client/internal/templates/pkce-auth-msg.html
Normal file
87
client/internal/templates/pkce-auth-msg.html
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background: #f7f8f9;
|
||||||
|
font-family: sans-serif, Arial, Tahoma;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e8e9ea;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
max-width: 550px;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 80px;
|
||||||
|
border-bottom: 1px solid #e8e9ea;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo img {
|
||||||
|
width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #525252;
|
||||||
|
line-height: 18px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content div {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: normal;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="https://img.mailinblue.com/6211297/images/content_library/original/64bd4ce82e1ea753e439b6a2.png">
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
{{ if .Error }}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="50" viewBox="0 0 100 100">
|
||||||
|
<circle cx="50" cy="50" r="45" fill="none" stroke="red" stroke-width="3"/>
|
||||||
|
<path d="M30 30 L70 70 M30 70 L70 30" fill="none" stroke="red" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
<div class="content">
|
||||||
|
<div>
|
||||||
|
Login failed
|
||||||
|
</div>
|
||||||
|
{{ .Error }}.
|
||||||
|
</div>
|
||||||
|
{{ else }}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="50" viewBox="0 0 100 100">
|
||||||
|
<circle cx="50" cy="50" r="45" fill="none" stroke="#5cb85c" stroke-width="3"/>
|
||||||
|
<path d="M30 50 L45 65 L70 35" fill="none" stroke="#5cb85c" stroke-width="5"/>
|
||||||
|
</svg>
|
||||||
|
<div class="content">
|
||||||
|
<div>
|
||||||
|
Login successful
|
||||||
|
</div>
|
||||||
|
Your device is now registered and logged in to NetBird.
|
||||||
|
<br>
|
||||||
|
You can now close this window.
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
120
client/internal/wgproxy/ebpf/bpf_bpfeb.go
Normal file
120
client/internal/wgproxy/ebpf/bpf_bpfeb.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Code generated by bpf2go; DO NOT EDIT.
|
||||||
|
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
|
||||||
|
// +build arm64be armbe mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64
|
||||||
|
|
||||||
|
package ebpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loadBpf returns the embedded CollectionSpec for bpf.
|
||||||
|
func loadBpf() (*ebpf.CollectionSpec, error) {
|
||||||
|
reader := bytes.NewReader(_BpfBytes)
|
||||||
|
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't load bpf: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBpfObjects loads bpf and converts it into a struct.
|
||||||
|
//
|
||||||
|
// The following types are suitable as obj argument:
|
||||||
|
//
|
||||||
|
// *bpfObjects
|
||||||
|
// *bpfPrograms
|
||||||
|
// *bpfMaps
|
||||||
|
//
|
||||||
|
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||||
|
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||||
|
spec, err := loadBpf()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec.LoadAndAssign(obj, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains maps and programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfSpecs struct {
|
||||||
|
bpfProgramSpecs
|
||||||
|
bpfMapSpecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfProgramSpecs struct {
|
||||||
|
XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfMapSpecs struct {
|
||||||
|
XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfObjects contains all objects after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfObjects struct {
|
||||||
|
bpfPrograms
|
||||||
|
bpfMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bpfObjects) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
&o.bpfPrograms,
|
||||||
|
&o.bpfMaps,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMaps contains all maps after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfMaps struct {
|
||||||
|
XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *bpfMaps) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
m.XdpPortMap,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfPrograms contains all programs after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfPrograms struct {
|
||||||
|
XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bpfPrograms) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
p.XdpProgFunc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _BpfClose(closers ...io.Closer) error {
|
||||||
|
for _, closer := range closers {
|
||||||
|
if err := closer.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not access this directly.
|
||||||
|
//
|
||||||
|
//go:embed bpf_bpfeb.o
|
||||||
|
var _BpfBytes []byte
|
||||||
BIN
client/internal/wgproxy/ebpf/bpf_bpfeb.o
Normal file
BIN
client/internal/wgproxy/ebpf/bpf_bpfeb.o
Normal file
Binary file not shown.
120
client/internal/wgproxy/ebpf/bpf_bpfel.go
Normal file
120
client/internal/wgproxy/ebpf/bpf_bpfel.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Code generated by bpf2go; DO NOT EDIT.
|
||||||
|
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64
|
||||||
|
// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64
|
||||||
|
|
||||||
|
package ebpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loadBpf returns the embedded CollectionSpec for bpf.
|
||||||
|
func loadBpf() (*ebpf.CollectionSpec, error) {
|
||||||
|
reader := bytes.NewReader(_BpfBytes)
|
||||||
|
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't load bpf: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadBpfObjects loads bpf and converts it into a struct.
|
||||||
|
//
|
||||||
|
// The following types are suitable as obj argument:
|
||||||
|
//
|
||||||
|
// *bpfObjects
|
||||||
|
// *bpfPrograms
|
||||||
|
// *bpfMaps
|
||||||
|
//
|
||||||
|
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
|
||||||
|
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
|
||||||
|
spec, err := loadBpf()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec.LoadAndAssign(obj, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains maps and programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfSpecs struct {
|
||||||
|
bpfProgramSpecs
|
||||||
|
bpfMapSpecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfSpecs contains programs before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfProgramSpecs struct {
|
||||||
|
XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
|
type bpfMapSpecs struct {
|
||||||
|
XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfObjects contains all objects after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfObjects struct {
|
||||||
|
bpfPrograms
|
||||||
|
bpfMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bpfObjects) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
&o.bpfPrograms,
|
||||||
|
&o.bpfMaps,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfMaps contains all maps after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfMaps struct {
|
||||||
|
XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *bpfMaps) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
m.XdpPortMap,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bpfPrograms contains all programs after they have been loaded into the kernel.
|
||||||
|
//
|
||||||
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
|
type bpfPrograms struct {
|
||||||
|
XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bpfPrograms) Close() error {
|
||||||
|
return _BpfClose(
|
||||||
|
p.XdpProgFunc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _BpfClose(closers ...io.Closer) error {
|
||||||
|
for _, closer := range closers {
|
||||||
|
if err := closer.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not access this directly.
|
||||||
|
//
|
||||||
|
//go:embed bpf_bpfel.o
|
||||||
|
var _BpfBytes []byte
|
||||||
BIN
client/internal/wgproxy/ebpf/bpf_bpfel.o
Normal file
BIN
client/internal/wgproxy/ebpf/bpf_bpfel.o
Normal file
Binary file not shown.
84
client/internal/wgproxy/ebpf/loader.go
Normal file
84
client/internal/wgproxy/ebpf/loader.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
//go:build linux && !android
|
||||||
|
|
||||||
|
package ebpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf/link"
|
||||||
|
"github.com/cilium/ebpf/rlimit"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mapKeyProxyPort uint32 = 0
|
||||||
|
mapKeyWgPort uint32 = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang-14 bpf src/portreplace.c --
|
||||||
|
|
||||||
|
// EBPF is a wrapper for eBPF program
|
||||||
|
type EBPF struct {
|
||||||
|
link link.Link
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEBPF create new EBPF instance
|
||||||
|
func NewEBPF() *EBPF {
|
||||||
|
return &EBPF{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load load ebpf program
|
||||||
|
func (l *EBPF) Load(proxyPort, wgPort int) error {
|
||||||
|
// it required for Docker
|
||||||
|
err := rlimit.RemoveMemlock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ifce, err := net.InterfaceByName("lo")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load pre-compiled programs into the kernel.
|
||||||
|
objs := bpfObjects{}
|
||||||
|
err = loadBpfObjects(&objs, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = objs.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = objs.XdpPortMap.Put(mapKeyProxyPort, uint16(proxyPort))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = objs.XdpPortMap.Put(mapKeyWgPort, uint16(wgPort))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = objs.XdpPortMap.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
l.link, err = link.AttachXDP(link.XDPOptions{
|
||||||
|
Program: objs.XdpProgFunc,
|
||||||
|
Interface: ifce.Index,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free free ebpf program
|
||||||
|
func (l *EBPF) Free() error {
|
||||||
|
if l.link != nil {
|
||||||
|
return l.link.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
18
client/internal/wgproxy/ebpf/loader_test.go
Normal file
18
client/internal/wgproxy/ebpf/loader_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package ebpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_newEBPF(t *testing.T) {
|
||||||
|
ebpf := NewEBPF()
|
||||||
|
err := ebpf.Load(1234, 51892)
|
||||||
|
defer func() {
|
||||||
|
_ = ebpf.Free()
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
90
client/internal/wgproxy/ebpf/src/portreplace.c
Normal file
90
client/internal/wgproxy/ebpf/src/portreplace.c
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include <linux/if_ether.h> // ETH_P_IP
|
||||||
|
#include <linux/udp.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
|
||||||
|
#define bpf_printk(fmt, ...) \
|
||||||
|
({ \
|
||||||
|
char ____fmt[] = fmt; \
|
||||||
|
bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
|
||||||
|
})
|
||||||
|
|
||||||
|
const __u32 map_key_proxy_port = 0;
|
||||||
|
const __u32 map_key_wg_port = 1;
|
||||||
|
|
||||||
|
struct bpf_map_def SEC("maps") xdp_port_map = {
|
||||||
|
.type = BPF_MAP_TYPE_ARRAY,
|
||||||
|
.key_size = sizeof(__u32),
|
||||||
|
.value_size = sizeof(__u16),
|
||||||
|
.max_entries = 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
__u16 proxy_port = 0;
|
||||||
|
__u16 wg_port = 0;
|
||||||
|
|
||||||
|
bool read_port_settings() {
|
||||||
|
__u16 *value;
|
||||||
|
value = bpf_map_lookup_elem(&xdp_port_map, &map_key_proxy_port);
|
||||||
|
if(!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy_port = *value;
|
||||||
|
|
||||||
|
value = bpf_map_lookup_elem(&xdp_port_map, &map_key_wg_port);
|
||||||
|
if(!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
wg_port = *value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("xdp")
|
||||||
|
int xdp_prog_func(struct xdp_md *ctx) {
|
||||||
|
if(proxy_port == 0 || wg_port == 0) {
|
||||||
|
if(!read_port_settings()){
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
||||||
|
bpf_printk("proxy port: %d, wg port: %d", proxy_port, wg_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *data = (void *)(long)ctx->data;
|
||||||
|
void *data_end = (void *)(long)ctx->data_end;
|
||||||
|
struct ethhdr *eth = data;
|
||||||
|
struct iphdr *ip = (data + sizeof(struct ethhdr));
|
||||||
|
struct udphdr *udp = (data + sizeof(struct ethhdr) + sizeof(struct iphdr));
|
||||||
|
|
||||||
|
// return early if not enough data
|
||||||
|
if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) > data_end){
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip non IPv4 packages
|
||||||
|
if (eth->h_proto != htons(ETH_P_IP)) {
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ip->protocol != IPPROTO_UDP) {
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2130706433 = 127.0.0.1
|
||||||
|
if (ip->daddr != htonl(2130706433)) {
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (udp->source != htons(wg_port)){
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
__be16 new_src_port = udp->dest;
|
||||||
|
__be16 new_dst_port = htons(proxy_port);
|
||||||
|
udp->dest = new_dst_port;
|
||||||
|
udp->source = new_src_port;
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
||||||
|
char _license[] SEC("license") = "GPL";
|
||||||
20
client/internal/wgproxy/factory.go
Normal file
20
client/internal/wgproxy/factory.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package wgproxy
|
||||||
|
|
||||||
|
type Factory struct {
|
||||||
|
wgPort int
|
||||||
|
ebpfProxy Proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Factory) GetProxy() Proxy {
|
||||||
|
if w.ebpfProxy != nil {
|
||||||
|
return w.ebpfProxy
|
||||||
|
}
|
||||||
|
return NewWGUserSpaceProxy(w.wgPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Factory) Free() error {
|
||||||
|
if w.ebpfProxy != nil {
|
||||||
|
return w.ebpfProxy.CloseConn()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
21
client/internal/wgproxy/factory_linux.go
Normal file
21
client/internal/wgproxy/factory_linux.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//go:build !android
|
||||||
|
|
||||||
|
package wgproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFactory(wgPort int) *Factory {
|
||||||
|
f := &Factory{wgPort: wgPort}
|
||||||
|
|
||||||
|
ebpfProxy := NewWGEBPFProxy(wgPort)
|
||||||
|
err := ebpfProxy.Listen()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to initialize ebpf proxy: %s", err)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
f.ebpfProxy = ebpfProxy
|
||||||
|
return f
|
||||||
|
}
|
||||||
7
client/internal/wgproxy/factory_nonlinux.go
Normal file
7
client/internal/wgproxy/factory_nonlinux.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !linux || android
|
||||||
|
|
||||||
|
package wgproxy
|
||||||
|
|
||||||
|
func NewFactory(wgPort int) *Factory {
|
||||||
|
return &Factory{wgPort: wgPort}
|
||||||
|
}
|
||||||
32
client/internal/wgproxy/portlookup.go
Normal file
32
client/internal/wgproxy/portlookup.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package wgproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
portRangeStart = 3128
|
||||||
|
portRangeEnd = 3228
|
||||||
|
)
|
||||||
|
|
||||||
|
type portLookup struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl portLookup) searchFreePort() (int, error) {
|
||||||
|
for i := portRangeStart; i <= portRangeEnd; i++ {
|
||||||
|
if pl.tryToBind(i) == nil {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("failed to bind free port for eBPF proxy")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl portLookup) tryToBind(port int) error {
|
||||||
|
l, err := net.ListenPacket("udp", fmt.Sprintf(":%d", port))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = l.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
42
client/internal/wgproxy/portlookup_test.go
Normal file
42
client/internal/wgproxy/portlookup_test.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package wgproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_portLookup_searchFreePort(t *testing.T) {
|
||||||
|
pl := portLookup{}
|
||||||
|
_, err := pl.searchFreePort()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_portLookup_on_allocated(t *testing.T) {
|
||||||
|
pl := portLookup{}
|
||||||
|
|
||||||
|
allocatedPort, err := allocatePort(portRangeStart)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer allocatedPort.Close()
|
||||||
|
|
||||||
|
fp, err := pl.searchFreePort()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fp != (portRangeStart + 1) {
|
||||||
|
t.Errorf("invalid free port, expected: %d, got: %d", portRangeStart+1, fp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func allocatePort(port int) (net.PacketConn, error) {
|
||||||
|
c, err := net.ListenPacket("udp", fmt.Sprintf(":%d", port))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
12
client/internal/wgproxy/proxy.go
Normal file
12
client/internal/wgproxy/proxy.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package wgproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Proxy is a transfer layer between the Turn connection and the WireGuard
|
||||||
|
type Proxy interface {
|
||||||
|
AddTurnConn(urnConn net.Conn) (net.Addr, error)
|
||||||
|
CloseConn() error
|
||||||
|
Free() error
|
||||||
|
}
|
||||||
252
client/internal/wgproxy/proxy_ebpf.go
Normal file
252
client/internal/wgproxy/proxy_ebpf.go
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
//go:build linux && !android
|
||||||
|
|
||||||
|
package wgproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
ebpf2 "github.com/netbirdio/netbird/client/internal/wgproxy/ebpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WGEBPFProxy definition for proxy with EBPF support
|
||||||
|
type WGEBPFProxy struct {
|
||||||
|
ebpf *ebpf2.EBPF
|
||||||
|
lastUsedPort uint16
|
||||||
|
localWGListenPort int
|
||||||
|
|
||||||
|
turnConnStore map[uint16]net.Conn
|
||||||
|
turnConnMutex sync.Mutex
|
||||||
|
|
||||||
|
rawConn net.PacketConn
|
||||||
|
conn *net.UDPConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWGEBPFProxy create new WGEBPFProxy instance
|
||||||
|
func NewWGEBPFProxy(wgPort int) *WGEBPFProxy {
|
||||||
|
log.Debugf("instantiate ebpf proxy")
|
||||||
|
wgProxy := &WGEBPFProxy{
|
||||||
|
localWGListenPort: wgPort,
|
||||||
|
ebpf: ebpf2.NewEBPF(),
|
||||||
|
lastUsedPort: 0,
|
||||||
|
turnConnStore: make(map[uint16]net.Conn),
|
||||||
|
}
|
||||||
|
return wgProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen load ebpf program and listen the proxy
|
||||||
|
func (p *WGEBPFProxy) Listen() error {
|
||||||
|
pl := portLookup{}
|
||||||
|
wgPorxyPort, err := pl.searchFreePort()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.rawConn, err = p.prepareSenderRawSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.ebpf.Load(wgPorxyPort, p.localWGListenPort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := net.UDPAddr{
|
||||||
|
Port: wgPorxyPort,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.conn, err = net.ListenUDP("udp", &addr)
|
||||||
|
if err != nil {
|
||||||
|
cErr := p.Free()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to close the wgproxy: %s", cErr)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go p.proxyToRemote()
|
||||||
|
log.Infof("local wg proxy listening on: %d", wgPorxyPort)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTurnConn add new turn connection for the proxy
|
||||||
|
func (p *WGEBPFProxy) AddTurnConn(turnConn net.Conn) (net.Addr, error) {
|
||||||
|
wgEndpointPort, err := p.storeTurnConn(turnConn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go p.proxyToLocal(wgEndpointPort, turnConn)
|
||||||
|
log.Infof("turn conn added to wg proxy store: %s, endpoint port: :%d", turnConn.RemoteAddr(), wgEndpointPort)
|
||||||
|
|
||||||
|
wgEndpoint := &net.UDPAddr{
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
Port: int(wgEndpointPort),
|
||||||
|
}
|
||||||
|
return wgEndpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseConn doing nothing because this type of proxy implementation does not store the connection
|
||||||
|
func (p *WGEBPFProxy) CloseConn() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free resources
|
||||||
|
func (p *WGEBPFProxy) Free() error {
|
||||||
|
var err1, err2, err3 error
|
||||||
|
if p.conn != nil {
|
||||||
|
err1 = p.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
err2 = p.ebpf.Free()
|
||||||
|
if p.rawConn != nil {
|
||||||
|
err3 = p.rawConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
|
||||||
|
if err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
|
return err3
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WGEBPFProxy) proxyToLocal(endpointPort uint16, remoteConn net.Conn) {
|
||||||
|
buf := make([]byte, 1500)
|
||||||
|
for {
|
||||||
|
n, err := remoteConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
log.Errorf("failed to read from turn conn (endpoint: :%d): %s", endpointPort, err)
|
||||||
|
}
|
||||||
|
p.removeTurnConn(endpointPort)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = p.sendPkg(buf[:n], endpointPort)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to write out turn pkg to local conn: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxyToRemote read messages from local WireGuard interface and forward it to remote conn
|
||||||
|
func (p *WGEBPFProxy) proxyToRemote() {
|
||||||
|
buf := make([]byte, 1500)
|
||||||
|
for {
|
||||||
|
n, addr, err := p.conn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to read UDP pkg from WG: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, ok := p.turnConnStore[uint16(addr.Port)]
|
||||||
|
if !ok {
|
||||||
|
log.Errorf("turn conn not found by port: %d", addr.Port)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to forward local wg pkg (%d) to remote turn conn: %s", addr.Port, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WGEBPFProxy) storeTurnConn(turnConn net.Conn) (uint16, error) {
|
||||||
|
p.turnConnMutex.Lock()
|
||||||
|
defer p.turnConnMutex.Unlock()
|
||||||
|
|
||||||
|
np, err := p.nextFreePort()
|
||||||
|
if err != nil {
|
||||||
|
return np, err
|
||||||
|
}
|
||||||
|
p.turnConnStore[np] = turnConn
|
||||||
|
return np, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WGEBPFProxy) removeTurnConn(turnConnID uint16) {
|
||||||
|
log.Tracef("remove turn conn from store by port: %d", turnConnID)
|
||||||
|
p.turnConnMutex.Lock()
|
||||||
|
defer p.turnConnMutex.Unlock()
|
||||||
|
delete(p.turnConnStore, turnConnID)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WGEBPFProxy) nextFreePort() (uint16, error) {
|
||||||
|
if len(p.turnConnStore) == 65535 {
|
||||||
|
return 0, fmt.Errorf("reached maximum turn connection numbers")
|
||||||
|
}
|
||||||
|
generatePort:
|
||||||
|
if p.lastUsedPort == 65535 {
|
||||||
|
p.lastUsedPort = 1
|
||||||
|
} else {
|
||||||
|
p.lastUsedPort++
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := p.turnConnStore[p.lastUsedPort]; ok {
|
||||||
|
goto generatePort
|
||||||
|
}
|
||||||
|
return p.lastUsedPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WGEBPFProxy) prepareSenderRawSocket() (net.PacketConn, error) {
|
||||||
|
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = syscall.SetsockoptString(fd, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, "lo")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.FilePacketConn(os.NewFile(uintptr(fd), fmt.Sprintf("fd %d", fd)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WGEBPFProxy) sendPkg(data []byte, port uint16) error {
|
||||||
|
localhost := net.ParseIP("127.0.0.1")
|
||||||
|
|
||||||
|
payload := gopacket.Payload(data)
|
||||||
|
ipH := &layers.IPv4{
|
||||||
|
DstIP: localhost,
|
||||||
|
SrcIP: localhost,
|
||||||
|
Version: 4,
|
||||||
|
TTL: 64,
|
||||||
|
Protocol: layers.IPProtocolUDP,
|
||||||
|
}
|
||||||
|
udpH := &layers.UDP{
|
||||||
|
SrcPort: layers.UDPPort(port),
|
||||||
|
DstPort: layers.UDPPort(p.localWGListenPort),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := udpH.SetNetworkLayerForChecksum(ipH)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
layerBuffer := gopacket.NewSerializeBuffer()
|
||||||
|
|
||||||
|
err = gopacket.SerializeLayers(layerBuffer, gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true}, ipH, udpH, payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = p.rawConn.WriteTo(layerBuffer.Bytes(), &net.IPAddr{IP: localhost})
|
||||||
|
return err
|
||||||
|
}
|
||||||
56
client/internal/wgproxy/proxy_ebpf_test.go
Normal file
56
client/internal/wgproxy/proxy_ebpf_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//go:build linux && !android
|
||||||
|
|
||||||
|
package wgproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWGEBPFProxy_connStore(t *testing.T) {
|
||||||
|
wgProxy := NewWGEBPFProxy(1)
|
||||||
|
|
||||||
|
p, _ := wgProxy.storeTurnConn(nil)
|
||||||
|
if p != 1 {
|
||||||
|
t.Errorf("invalid initial port: %d", wgProxy.lastUsedPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
numOfConns := 10
|
||||||
|
for i := 0; i < numOfConns; i++ {
|
||||||
|
p, _ = wgProxy.storeTurnConn(nil)
|
||||||
|
}
|
||||||
|
if p != uint16(numOfConns)+1 {
|
||||||
|
t.Errorf("invalid last used port: %d, expected: %d", p, numOfConns+1)
|
||||||
|
}
|
||||||
|
if len(wgProxy.turnConnStore) != numOfConns+1 {
|
||||||
|
t.Errorf("invalid store size: %d, expected: %d", len(wgProxy.turnConnStore), numOfConns+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWGEBPFProxy_portCalculation_overflow(t *testing.T) {
|
||||||
|
wgProxy := NewWGEBPFProxy(1)
|
||||||
|
|
||||||
|
_, _ = wgProxy.storeTurnConn(nil)
|
||||||
|
wgProxy.lastUsedPort = 65535
|
||||||
|
p, _ := wgProxy.storeTurnConn(nil)
|
||||||
|
|
||||||
|
if len(wgProxy.turnConnStore) != 2 {
|
||||||
|
t.Errorf("invalid store size: %d, expected: %d", len(wgProxy.turnConnStore), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p != 2 {
|
||||||
|
t.Errorf("invalid last used port: %d, expected: %d", p, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWGEBPFProxy_portCalculation_maxConn(t *testing.T) {
|
||||||
|
wgProxy := NewWGEBPFProxy(1)
|
||||||
|
|
||||||
|
for i := 0; i < 65535; i++ {
|
||||||
|
_, _ = wgProxy.storeTurnConn(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := wgProxy.storeTurnConn(nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("invalid turn conn store calculation")
|
||||||
|
}
|
||||||
|
}
|
||||||
105
client/internal/wgproxy/proxy_userspace.go
Normal file
105
client/internal/wgproxy/proxy_userspace.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package wgproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WGUserSpaceProxy proxies
|
||||||
|
type WGUserSpaceProxy struct {
|
||||||
|
localWGListenPort int
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
|
||||||
|
remoteConn net.Conn
|
||||||
|
localConn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWGUserSpaceProxy instantiate a user space WireGuard proxy
|
||||||
|
func NewWGUserSpaceProxy(wgPort int) *WGUserSpaceProxy {
|
||||||
|
p := &WGUserSpaceProxy{
|
||||||
|
localWGListenPort: wgPort,
|
||||||
|
}
|
||||||
|
p.ctx, p.cancel = context.WithCancel(context.Background())
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTurnConn start the proxy with the given remote conn
|
||||||
|
func (p *WGUserSpaceProxy) AddTurnConn(remoteConn net.Conn) (net.Addr, error) {
|
||||||
|
p.remoteConn = remoteConn
|
||||||
|
|
||||||
|
var err error
|
||||||
|
p.localConn, err = net.Dial("udp", fmt.Sprintf(":%d", p.localWGListenPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed dialing to local Wireguard port %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go p.proxyToRemote()
|
||||||
|
go p.proxyToLocal()
|
||||||
|
|
||||||
|
return p.localConn.LocalAddr(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseConn close the localConn
|
||||||
|
func (p *WGUserSpaceProxy) CloseConn() error {
|
||||||
|
p.cancel()
|
||||||
|
if p.localConn == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return p.localConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free doing nothing because this implementation of proxy does not have global state
|
||||||
|
func (p *WGUserSpaceProxy) Free() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxyToRemote proxies everything from Wireguard to the RemoteKey peer
|
||||||
|
// blocks
|
||||||
|
func (p *WGUserSpaceProxy) proxyToRemote() {
|
||||||
|
|
||||||
|
buf := make([]byte, 1500)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-p.ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
n, err := p.localConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = p.remoteConn.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxyToLocal proxies everything from the RemoteKey peer to local Wireguard
|
||||||
|
// blocks
|
||||||
|
func (p *WGUserSpaceProxy) proxyToLocal() {
|
||||||
|
|
||||||
|
buf := make([]byte, 1500)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-p.ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
n, err := p.remoteConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = p.localConn.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -38,8 +39,8 @@ type Server struct {
|
|||||||
|
|
||||||
type oauthAuthFlow struct {
|
type oauthAuthFlow struct {
|
||||||
expiresAt time.Time
|
expiresAt time.Time
|
||||||
client internal.OAuthClient
|
flow auth.OAuthFlow
|
||||||
info internal.DeviceAuthInfo
|
info auth.AuthFlowInfo
|
||||||
waitCancel context.CancelFunc
|
waitCancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,28 +207,15 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
|||||||
state.Set(internal.StatusConnecting)
|
state.Set(internal.StatusConnecting)
|
||||||
|
|
||||||
if msg.SetupKey == "" {
|
if msg.SetupKey == "" {
|
||||||
providerConfig, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
|
oAuthFlow, err := auth.NewOAuthFlow(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.Set(internal.StatusLoginFailed)
|
state.Set(internal.StatusLoginFailed)
|
||||||
s, ok := gstatus.FromError(err)
|
return nil, 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hostedClient := internal.NewHostedDeviceFlow(providerConfig.ProviderConfig)
|
if s.oauthAuthFlow.flow != nil && s.oauthAuthFlow.flow.GetClientID(ctx) == oAuthFlow.GetClientID(context.TODO()) {
|
||||||
|
|
||||||
if s.oauthAuthFlow.client != nil && s.oauthAuthFlow.client.GetClientID(ctx) == hostedClient.GetClientID(context.TODO()) {
|
|
||||||
if s.oauthAuthFlow.expiresAt.After(time.Now().Add(90 * time.Second)) {
|
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{
|
return &proto.LoginResponse{
|
||||||
NeedsSSOLogin: true,
|
NeedsSSOLogin: true,
|
||||||
VerificationURI: s.oauthAuthFlow.info.VerificationURI,
|
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 {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
s.oauthAuthFlow.client = hostedClient
|
s.oauthAuthFlow.flow = oAuthFlow
|
||||||
s.oauthAuthFlow.info = deviceAuthInfo
|
s.oauthAuthFlow.info = authInfo
|
||||||
s.oauthAuthFlow.expiresAt = time.Now().Add(time.Duration(deviceAuthInfo.ExpiresIn) * time.Second)
|
s.oauthAuthFlow.expiresAt = time.Now().Add(time.Duration(authInfo.ExpiresIn) * time.Second)
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
state.Set(internal.StatusNeedsLogin)
|
state.Set(internal.StatusNeedsLogin)
|
||||||
|
|
||||||
return &proto.LoginResponse{
|
return &proto.LoginResponse{
|
||||||
NeedsSSOLogin: true,
|
NeedsSSOLogin: true,
|
||||||
VerificationURI: deviceAuthInfo.VerificationURI,
|
VerificationURI: authInfo.VerificationURI,
|
||||||
VerificationURIComplete: deviceAuthInfo.VerificationURIComplete,
|
VerificationURIComplete: authInfo.VerificationURIComplete,
|
||||||
UserCode: deviceAuthInfo.UserCode,
|
UserCode: authInfo.UserCode,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,8 +277,8 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin
|
|||||||
s.actCancel = cancel
|
s.actCancel = cancel
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
if s.oauthAuthFlow.client == nil {
|
if s.oauthAuthFlow.flow == nil {
|
||||||
return nil, gstatus.Errorf(codes.Internal, "oauth client is not initialized")
|
return nil, gstatus.Errorf(codes.Internal, "oauth flow is not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
state := internal.CtxGetState(ctx)
|
state := internal.CtxGetState(ctx)
|
||||||
@@ -304,10 +292,10 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin
|
|||||||
state.Set(internal.StatusConnecting)
|
state.Set(internal.StatusConnecting)
|
||||||
|
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
deviceAuthInfo := s.oauthAuthFlow.info
|
flowInfo := s.oauthAuthFlow.info
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
if deviceAuthInfo.UserCode != msg.UserCode {
|
if flowInfo.UserCode != msg.UserCode {
|
||||||
state.Set(internal.StatusLoginFailed)
|
state.Set(internal.StatusLoginFailed)
|
||||||
return nil, gstatus.Errorf(codes.InvalidArgument, "sso user code is invalid")
|
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.oauthAuthFlow.waitCancel = cancel
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
tokenInfo, err := s.oauthAuthFlow.client.WaitToken(waitCTX, deviceAuthInfo)
|
tokenInfo, err := s.oauthAuthFlow.flow.WaitToken(waitCTX, flowInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == context.Canceled {
|
if err == context.Canceled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -30,6 +30,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
fyne.io/fyne/v2 v2.1.4
|
fyne.io/fyne/v2 v2.1.4
|
||||||
github.com/c-robinson/iplib v1.0.3
|
github.com/c-robinson/iplib v1.0.3
|
||||||
|
github.com/cilium/ebpf v0.10.0
|
||||||
github.com/coreos/go-iptables v0.6.0
|
github.com/coreos/go-iptables v0.6.0
|
||||||
github.com/creack/pty v1.1.18
|
github.com/creack/pty v1.1.18
|
||||||
github.com/eko/gocache/v3 v3.1.1
|
github.com/eko/gocache/v3 v3.1.1
|
||||||
@@ -125,7 +126,6 @@ require (
|
|||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
|
||||||
github.com/spf13/cast v1.5.0 // indirect
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
|
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
|
||||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
|
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -101,6 +101,8 @@ github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmE
|
|||||||
github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||||
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||||
|
github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ=
|
||||||
|
github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
@@ -177,7 +179,7 @@ github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQL
|
|||||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
|
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
|
||||||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
|
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
@@ -419,7 +421,7 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
|
|||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
@@ -552,7 +554,6 @@ github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI=
|
|||||||
github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs=
|
github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs=
|
||||||
github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54=
|
github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54=
|
||||||
github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8=
|
github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -596,8 +597,7 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
|
|||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
|
||||||
github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so=
|
github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so=
|
||||||
github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM=
|
github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM=
|
||||||
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
|
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func (m *UniversalUDPMuxDefault) ReadFromConn(ctx context.Context) {
|
|||||||
default:
|
default:
|
||||||
_, a, err := m.params.UDPConn.ReadFrom(buf)
|
_, a, err := m.params.UDPConn.ReadFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error while reading packet %s", err)
|
log.Errorf("error while reading packet: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
msg := &stun.Message{
|
msg := &stun.Message{
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func (w *WGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.D
|
|||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
log.Debugf("updating interface %s peer %s: endpoint %s ", w.tun.DeviceName(), peerKey, endpoint)
|
log.Debugf("updating interface %s peer %s, endpoint %s ", w.tun.DeviceName(), peerKey, endpoint)
|
||||||
return w.configurer.updatePeer(peerKey, allowedIps, keepAlive, endpoint, preSharedKey)
|
return w.configurer.updatePeer(peerKey, allowedIps, keepAlive, endpoint, preSharedKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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_DISABLE_ANONYMOUS_METRICS=${NETBIRD_DISABLE_ANONYMOUS_METRICS:-false}
|
||||||
NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken}
|
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
|
# exports
|
||||||
export NETBIRD_DOMAIN
|
export NETBIRD_DOMAIN
|
||||||
export NETBIRD_AUTH_CLIENT_ID
|
export NETBIRD_AUTH_CLIENT_ID
|
||||||
@@ -80,4 +84,6 @@ export NETBIRD_AUTH_USER_ID_CLAIM
|
|||||||
export NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE
|
export NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE
|
||||||
export NETBIRD_TOKEN_SOURCE
|
export NETBIRD_TOKEN_SOURCE
|
||||||
export NETBIRD_AUTH_DEVICE_AUTH_SCOPE
|
export NETBIRD_AUTH_DEVICE_AUTH_SCOPE
|
||||||
export NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN
|
export NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN
|
||||||
|
export NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT
|
||||||
|
export NETBIRD_AUTH_PKCE_USE_ID_TOKEN
|
||||||
@@ -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_JWT_CERTS=$(jq -r '.jwks_uri' openid-configuration.json)
|
||||||
export NETBIRD_AUTH_TOKEN_ENDPOINT=$(jq -r '.token_endpoint' 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_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
|
if [[ ! -z "${NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID}" ]]; then
|
||||||
# user enabled Device Authorization Grant feature
|
# user enabled Device Authorization Grant feature
|
||||||
export NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="hosted"
|
export NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="hosted"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$NETBIRD_TOKEN_SOURCE" = "idToken" ]; then
|
||||||
|
export NETBIRD_AUTH_PKCE_USE_ID_TOKEN=true
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if letsencrypt was disabled
|
# Check if letsencrypt was disabled
|
||||||
if [[ "$NETBIRD_DISABLE_LETSENCRYPT" == "true" ]]; then
|
if [[ "$NETBIRD_DISABLE_LETSENCRYPT" == "true" ]]; then
|
||||||
export NETBIRD_DASHBOARD_ENDPOINT="https://$NETBIRD_DOMAIN:443"
|
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
|
export NETBIRD_IDP_MGMT_EXTRA_CONFIG=$EXTRA_CONFIG
|
||||||
fi
|
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
|
env | grep NETBIRD
|
||||||
|
|
||||||
envsubst <docker-compose.yml.tmpl >docker-compose.yml
|
envsubst <docker-compose.yml.tmpl >docker-compose.yml
|
||||||
|
|||||||
735
infrastructure_files/getting-started-with-zitadel.sh
Normal file
735
infrastructure_files/getting-started-with-zitadel.sh
Normal file
@@ -0,0 +1,735 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
handle_request_command_status() {
|
||||||
|
PARSED_RESPONSE=$1
|
||||||
|
FUNCTION_NAME=$2
|
||||||
|
RESPONSE=$3
|
||||||
|
if [[ $PARSED_RESPONSE -ne 0 ]]; then
|
||||||
|
echo "ERROR calling $FUNCTION_NAME:" $(echo "$RESPONSE" | jq -r '.message') > /dev/stderr
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_zitadel_request_response() {
|
||||||
|
PARSED_RESPONSE=$1
|
||||||
|
FUNCTION_NAME=$2
|
||||||
|
RESPONSE=$3
|
||||||
|
if [[ $PARSED_RESPONSE == "null" ]]; then
|
||||||
|
echo "ERROR calling $FUNCTION_NAME:" $(echo "$RESPONSE" | jq -r '.message') > /dev/stderr
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
}
|
||||||
|
|
||||||
|
check_docker_compose() {
|
||||||
|
if command -v docker-compose &> /dev/null
|
||||||
|
then
|
||||||
|
echo "docker-compose"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if docker compose --help &> /dev/null
|
||||||
|
then
|
||||||
|
echo "docker compose"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "docker-compose is not installed or not in PATH. Please follow the steps from the official guide: https://docs.docker.com/engine/install/" > /dev/stderr
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
check_jq() {
|
||||||
|
if ! command -v jq &> /dev/null
|
||||||
|
then
|
||||||
|
echo "jq is not installed or not in PATH, please install with your package manager. e.g. sudo apt install jq" > /dev/stderr
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_pgdb() {
|
||||||
|
set +e
|
||||||
|
while true; do
|
||||||
|
if $DOCKER_COMPOSE_COMMAND exec -T pgdb pg_isready -U postgres; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo -n " ."
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
echo " done"
|
||||||
|
set -e
|
||||||
|
}
|
||||||
|
|
||||||
|
init_pgdb() {
|
||||||
|
echo -e "\nInitializing Zitadel's CockroachDB\n\n"
|
||||||
|
$DOCKER_COMPOSE_COMMAND up -d pgdb
|
||||||
|
echo ""
|
||||||
|
# shellcheck disable=SC2028
|
||||||
|
echo -n "Waiting cockroachDB to become ready "
|
||||||
|
wait_pgdb
|
||||||
|
#$DOCKER_COMPOSE_COMMAND exec -T pgdb /bin/bash -c "cp /cockroach/certs/* /zitadel-certs/ && cockroach cert create-client --overwrite --certs-dir /zitadel-certs/ --ca-key /zitadel-certs/ca.key zitadel_user && chown -R 1000:1000 /zitadel-certs/"
|
||||||
|
handle_request_command_status $? "init_pgdb failed" ""
|
||||||
|
}
|
||||||
|
|
||||||
|
get_main_ip_address() {
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
interface=$(route -n get default | grep 'interface:' | awk '{print $2}')
|
||||||
|
ip_address=$(ifconfig "$interface" | grep 'inet ' | awk '{print $2}')
|
||||||
|
else
|
||||||
|
interface=$(ip route | grep default | awk '{print $5}' | head -n 1)
|
||||||
|
ip_address=$(ip addr show "$interface" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$ip_address"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_pat() {
|
||||||
|
PAT_PATH=$1
|
||||||
|
set +e
|
||||||
|
while true; do
|
||||||
|
if [[ -f "$PAT_PATH" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo -n " ."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo " done"
|
||||||
|
set -e
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_api() {
|
||||||
|
INSTANCE_URL=$1
|
||||||
|
PAT=$2
|
||||||
|
set +e
|
||||||
|
while true; do
|
||||||
|
curl -s --fail -o /dev/null "$INSTANCE_URL/auth/v1/users/me" -H "Authorization: Bearer $PAT"
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo -n " ."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo " done"
|
||||||
|
set -e
|
||||||
|
}
|
||||||
|
|
||||||
|
create_new_project() {
|
||||||
|
INSTANCE_URL=$1
|
||||||
|
PAT=$2
|
||||||
|
PROJECT_NAME="NETBIRD"
|
||||||
|
|
||||||
|
RESPONSE=$(
|
||||||
|
curl -sS -X POST "$INSTANCE_URL/management/v1/projects" \
|
||||||
|
-H "Authorization: Bearer $PAT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name": "'"$PROJECT_NAME"'"}'
|
||||||
|
)
|
||||||
|
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.id')
|
||||||
|
handle_zitadel_request_response "$PARSED_RESPONSE" "create_new_project" "$RESPONSE"
|
||||||
|
echo "$PARSED_RESPONSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_new_application() {
|
||||||
|
INSTANCE_URL=$1
|
||||||
|
PAT=$2
|
||||||
|
APPLICATION_NAME=$3
|
||||||
|
BASE_REDIRECT_URL1=$4
|
||||||
|
BASE_REDIRECT_URL2=$5
|
||||||
|
ZITADEL_DEV_MODE=$6
|
||||||
|
|
||||||
|
RESPONSE=$(
|
||||||
|
curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \
|
||||||
|
-H "Authorization: Bearer $PAT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"name": "'"$APPLICATION_NAME"'",
|
||||||
|
"redirectUris": [
|
||||||
|
"'"$BASE_REDIRECT_URL1"'",
|
||||||
|
"'"$BASE_REDIRECT_URL2"'"
|
||||||
|
],
|
||||||
|
"postLogoutRedirectUris": [
|
||||||
|
"'"$BASE_REDIRECT_URL1"'"
|
||||||
|
],
|
||||||
|
"RESPONSETypes": [
|
||||||
|
"OIDC_RESPONSE_TYPE_CODE"
|
||||||
|
],
|
||||||
|
"grantTypes": [
|
||||||
|
"OIDC_GRANT_TYPE_AUTHORIZATION_CODE",
|
||||||
|
"OIDC_GRANT_TYPE_REFRESH_TOKEN"
|
||||||
|
],
|
||||||
|
"appType": "OIDC_APP_TYPE_USER_AGENT",
|
||||||
|
"authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE",
|
||||||
|
"version": "OIDC_VERSION_1_0",
|
||||||
|
"devMode": '"$ZITADEL_DEV_MODE"',
|
||||||
|
"accessTokenType": "OIDC_TOKEN_TYPE_JWT",
|
||||||
|
"accessTokenRoleAssertion": true,
|
||||||
|
"skipNativeAppSuccessPage": true
|
||||||
|
}'
|
||||||
|
)
|
||||||
|
|
||||||
|
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.clientId')
|
||||||
|
handle_zitadel_request_response "$PARSED_RESPONSE" "create_new_application" "$RESPONSE"
|
||||||
|
echo "$PARSED_RESPONSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_service_user() {
|
||||||
|
INSTANCE_URL=$1
|
||||||
|
PAT=$2
|
||||||
|
|
||||||
|
RESPONSE=$(
|
||||||
|
curl -sS -X POST "$INSTANCE_URL/management/v1/users/machine" \
|
||||||
|
-H "Authorization: Bearer $PAT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"userName": "netbird-service-account",
|
||||||
|
"name": "Netbird Service Account",
|
||||||
|
"description": "Netbird Service Account for IDP management",
|
||||||
|
"accessTokenType": "ACCESS_TOKEN_TYPE_JWT"
|
||||||
|
}'
|
||||||
|
)
|
||||||
|
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.userId')
|
||||||
|
handle_zitadel_request_response "$PARSED_RESPONSE" "create_service_user" "$RESPONSE"
|
||||||
|
echo "$PARSED_RESPONSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_service_user_secret() {
|
||||||
|
INSTANCE_URL=$1
|
||||||
|
PAT=$2
|
||||||
|
USER_ID=$3
|
||||||
|
|
||||||
|
RESPONSE=$(
|
||||||
|
curl -sS -X PUT "$INSTANCE_URL/management/v1/users/$USER_ID/secret" \
|
||||||
|
-H "Authorization: Bearer $PAT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{}'
|
||||||
|
)
|
||||||
|
SERVICE_USER_CLIENT_ID=$(echo "$RESPONSE" | jq -r '.clientId')
|
||||||
|
handle_zitadel_request_response "$SERVICE_USER_CLIENT_ID" "create_service_user_secret_id" "$RESPONSE"
|
||||||
|
SERVICE_USER_CLIENT_SECRET=$(echo "$RESPONSE" | jq -r '.clientSecret')
|
||||||
|
handle_zitadel_request_response "$SERVICE_USER_CLIENT_SECRET" "create_service_user_secret" "$RESPONSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_organization_user_manager() {
|
||||||
|
INSTANCE_URL=$1
|
||||||
|
PAT=$2
|
||||||
|
USER_ID=$3
|
||||||
|
|
||||||
|
RESPONSE=$(
|
||||||
|
curl -sS -X POST "$INSTANCE_URL/management/v1/orgs/me/members" \
|
||||||
|
-H "Authorization: Bearer $PAT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"userId": "'"$USER_ID"'",
|
||||||
|
"roles": [
|
||||||
|
"ORG_USER_MANAGER"
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
)
|
||||||
|
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.creationDate')
|
||||||
|
handle_zitadel_request_response "$PARSED_RESPONSE" "add_organization_user_manager" "$RESPONSE"
|
||||||
|
echo "$PARSED_RESPONSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_admin_user() {
|
||||||
|
INSTANCE_URL=$1
|
||||||
|
PAT=$2
|
||||||
|
USERNAME=$3
|
||||||
|
PASSWORD=$4
|
||||||
|
RESPONSE=$(
|
||||||
|
curl -sS -X POST "$INSTANCE_URL/management/v1/users/human/_import" \
|
||||||
|
-H "Authorization: Bearer $PAT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"userName": "'"$USERNAME"'",
|
||||||
|
"profile": {
|
||||||
|
"firstName": "Zitadel",
|
||||||
|
"lastName": "Admin"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"email": "'"$USERNAME"'",
|
||||||
|
"isEmailVerified": true
|
||||||
|
},
|
||||||
|
"password": "'"$PASSWORD"'",
|
||||||
|
"passwordChangeRequired": true
|
||||||
|
}'
|
||||||
|
)
|
||||||
|
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.userId')
|
||||||
|
handle_zitadel_request_response "$PARSED_RESPONSE" "create_admin_user" "$RESPONSE"
|
||||||
|
echo "$PARSED_RESPONSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_instance_admin() {
|
||||||
|
INSTANCE_URL=$1
|
||||||
|
PAT=$2
|
||||||
|
USER_ID=$3
|
||||||
|
|
||||||
|
RESPONSE=$(
|
||||||
|
curl -sS -X POST "$INSTANCE_URL/admin/v1/members" \
|
||||||
|
-H "Authorization: Bearer $PAT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"userId": "'"$USER_ID"'",
|
||||||
|
"roles": [
|
||||||
|
"IAM_OWNER"
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
)
|
||||||
|
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.creationDate')
|
||||||
|
handle_zitadel_request_response "$PARSED_RESPONSE" "add_instance_admin" "$RESPONSE"
|
||||||
|
echo "$PARSED_RESPONSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_auto_service_user() {
|
||||||
|
INSTANCE_URL=$1
|
||||||
|
PAT=$2
|
||||||
|
|
||||||
|
RESPONSE=$(
|
||||||
|
curl -sS -X GET "$INSTANCE_URL/auth/v1/users/me" \
|
||||||
|
-H "Authorization: Bearer $PAT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
)
|
||||||
|
USER_ID=$(echo "$RESPONSE" | jq -r '.user.id')
|
||||||
|
handle_zitadel_request_response "$USER_ID" "delete_auto_service_user_get_user" "$RESPONSE"
|
||||||
|
|
||||||
|
RESPONSE=$(
|
||||||
|
curl -sS -X DELETE "$INSTANCE_URL/admin/v1/members/$USER_ID" \
|
||||||
|
-H "Authorization: Bearer $PAT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
)
|
||||||
|
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.changeDate')
|
||||||
|
handle_zitadel_request_response "$PARSED_RESPONSE" "delete_auto_service_user_remove_instance_permissions" "$RESPONSE"
|
||||||
|
|
||||||
|
RESPONSE=$(
|
||||||
|
curl -sS -X DELETE "$INSTANCE_URL/management/v1/orgs/me/members/$USER_ID" \
|
||||||
|
-H "Authorization: Bearer $PAT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
)
|
||||||
|
PARSED_RESPONSE=$(echo "$RESPONSE" | jq -r '.details.changeDate')
|
||||||
|
handle_zitadel_request_response "$PARSED_RESPONSE" "delete_auto_service_user_remove_org_permissions" "$RESPONSE"
|
||||||
|
echo "$PARSED_RESPONSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
init_zitadel() {
|
||||||
|
echo -e "\nInitializing Zitadel with NetBird's applications\n"
|
||||||
|
INSTANCE_URL="$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT"
|
||||||
|
|
||||||
|
TOKEN_PATH=./machinekey/zitadel-admin-sa.token
|
||||||
|
|
||||||
|
echo -n "Waiting for Zitadel's PAT to be created "
|
||||||
|
wait_pat "$TOKEN_PATH"
|
||||||
|
echo "Reading Zitadel PAT"
|
||||||
|
PAT=$(cat $TOKEN_PATH)
|
||||||
|
if [ "$PAT" = "null" ]; then
|
||||||
|
echo "Failed requesting getting Zitadel PAT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "Waiting for Zitadel to become ready "
|
||||||
|
wait_api "$INSTANCE_URL" "$PAT"
|
||||||
|
|
||||||
|
# create the zitadel project
|
||||||
|
echo "Creating new zitadel project"
|
||||||
|
PROJECT_ID=$(create_new_project "$INSTANCE_URL" "$PAT")
|
||||||
|
|
||||||
|
ZITADEL_DEV_MODE=false
|
||||||
|
BASE_REDIRECT_URL=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN
|
||||||
|
if [[ $NETBIRD_HTTP_PROTOCOL == "http" ]]; then
|
||||||
|
ZITADEL_DEV_MODE=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# create zitadel spa applications
|
||||||
|
echo "Creating new Zitadel SPA Dashboard application"
|
||||||
|
DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$ZITADEL_DEV_MODE")
|
||||||
|
|
||||||
|
echo "Creating new Zitadel SPA Cli application"
|
||||||
|
CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "true")
|
||||||
|
|
||||||
|
MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT")
|
||||||
|
|
||||||
|
SERVICE_USER_CLIENT_ID="null"
|
||||||
|
SERVICE_USER_CLIENT_SECRET="null"
|
||||||
|
|
||||||
|
create_service_user_secret "$INSTANCE_URL" "$PAT" "$MACHINE_USER_ID"
|
||||||
|
|
||||||
|
DATE=$(add_organization_user_manager "$INSTANCE_URL" "$PAT" "$MACHINE_USER_ID")
|
||||||
|
|
||||||
|
ZITADEL_ADMIN_USERNAME="admin@$NETBIRD_DOMAIN"
|
||||||
|
ZITADEL_ADMIN_PASSWORD="$(openssl rand -base64 32 | sed 's/=//g')@"
|
||||||
|
|
||||||
|
HUMAN_USER_ID=$(create_admin_user "$INSTANCE_URL" "$PAT" "$ZITADEL_ADMIN_USERNAME" "$ZITADEL_ADMIN_PASSWORD")
|
||||||
|
|
||||||
|
DATE="null"
|
||||||
|
|
||||||
|
DATE=$(add_instance_admin "$INSTANCE_URL" "$PAT" "$HUMAN_USER_ID")
|
||||||
|
|
||||||
|
DATE="null"
|
||||||
|
DATE=$(delete_auto_service_user "$INSTANCE_URL" "$PAT")
|
||||||
|
if [ "$DATE" = "null" ]; then
|
||||||
|
echo "Failed deleting auto service user"
|
||||||
|
echo "Please remove it manually"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export NETBIRD_AUTH_CLIENT_ID=$DASHBOARD_APPLICATION_CLIENT_ID
|
||||||
|
export NETBIRD_AUTH_CLIENT_ID_CLI=$CLI_APPLICATION_CLIENT_ID
|
||||||
|
export NETBIRD_IDP_MGMT_CLIENT_ID=$SERVICE_USER_CLIENT_ID
|
||||||
|
export NETBIRD_IDP_MGMT_CLIENT_SECRET=$SERVICE_USER_CLIENT_SECRET
|
||||||
|
export ZITADEL_ADMIN_USERNAME
|
||||||
|
export ZITADEL_ADMIN_PASSWORD
|
||||||
|
}
|
||||||
|
|
||||||
|
initEnvironment() {
|
||||||
|
CADDY_SECURE_DOMAIN=""
|
||||||
|
ZITADEL_EXTERNALSECURE="false"
|
||||||
|
ZITADEL_TLS_MODE="disabled"
|
||||||
|
ZITADEL_MASTERKEY="$(openssl rand -base64 32 | head -c 32)"
|
||||||
|
USING_DOMAIN="true"
|
||||||
|
NETBIRD_PORT=80
|
||||||
|
NETBIRD_HTTP_PROTOCOL="http"
|
||||||
|
TURN_USER="self"
|
||||||
|
TURN_PASSWORD=$(openssl rand -base64 32 | sed 's/=//g')
|
||||||
|
TURN_MIN_PORT=49152
|
||||||
|
TURN_MAX_PORT=65535
|
||||||
|
|
||||||
|
NETBIRD_DOMAIN=$NETBIRD_DOMAIN
|
||||||
|
if [ "$NETBIRD_DOMAIN-x" == "-x" ] ; then
|
||||||
|
echo "NETBIRD_DOMAIN is not set, using the main IP address"
|
||||||
|
NETBIRD_DOMAIN=$(get_main_ip_address)
|
||||||
|
USING_DOMAIN="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$NETBIRD_DOMAIN" == "localhost" ]; then
|
||||||
|
USING_DOMAIN="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $USING_DOMAIN == "true" ]; then
|
||||||
|
ZITADEL_EXTERNALSECURE="true"
|
||||||
|
ZITADEL_TLS_MODE="external"
|
||||||
|
NETBIRD_PORT=443
|
||||||
|
CADDY_SECURE_DOMAIN=", $NETBIRD_DOMAIN:$NETBIRD_PORT"
|
||||||
|
NETBIRD_HTTP_PROTOCOL="https"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
ZIDATE_TOKEN_EXPIRATION_DATE=$(date -u -v+30M "+%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
else
|
||||||
|
ZIDATE_TOKEN_EXPIRATION_DATE=$(date -u -d "+30 minutes" "+%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_jq
|
||||||
|
|
||||||
|
DOCKER_COMPOSE_COMMAND=$(check_docker_compose)
|
||||||
|
|
||||||
|
if [ -f zitadel.env ]; then
|
||||||
|
echo "Generated files already exist, if you want to reinitialize the environment, please remove them first."
|
||||||
|
echo "You can use the following commands:"
|
||||||
|
echo " $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes"
|
||||||
|
echo " rm -f docker-compose.yml Caddyfile zitadel.env dashboard.env machinekey/zitadel-admin-sa.token turnserver.conf management.json"
|
||||||
|
echo "Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo Rendering initial files...
|
||||||
|
renderDockerCompose > docker-compose.yml
|
||||||
|
renderCaddyfile > Caddyfile
|
||||||
|
renderZitadelEnv > zitadel.env
|
||||||
|
echo "" > dashboard.env
|
||||||
|
echo "" > turnserver.conf
|
||||||
|
echo "" > management.json
|
||||||
|
|
||||||
|
mkdir -p machinekey
|
||||||
|
chmod 777 machinekey
|
||||||
|
|
||||||
|
init_pgdb
|
||||||
|
|
||||||
|
echo -e "\nStarting Zidatel IDP for user management\n\n"
|
||||||
|
$DOCKER_COMPOSE_COMMAND up -d caddy zitadel
|
||||||
|
init_zitadel
|
||||||
|
|
||||||
|
echo -e "\nRendering NetBird files...\n"
|
||||||
|
renderTurnServerConf > turnserver.conf
|
||||||
|
renderManagementJson > management.json
|
||||||
|
renderDashboardEnv > dashboard.env
|
||||||
|
|
||||||
|
echo -e "\nStarting NetBird services\n"
|
||||||
|
$DOCKER_COMPOSE_COMMAND up -d
|
||||||
|
echo -e "\nDone!\n"
|
||||||
|
echo "You can access the NetBird dashboard at $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT"
|
||||||
|
echo "Login with the following credentials:"
|
||||||
|
echo "Username: $ZITADEL_ADMIN_USERNAME" | tee .env
|
||||||
|
echo "Password: $ZITADEL_ADMIN_PASSWORD" | tee -a .env
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCaddyfile() {
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
debug
|
||||||
|
servers :80,:443 {
|
||||||
|
protocols h1 h2c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:80${CADDY_SECURE_DOMAIN} {
|
||||||
|
# Signal
|
||||||
|
reverse_proxy /signalexchange.SignalExchange/* h2c://signal:10000
|
||||||
|
# Management
|
||||||
|
reverse_proxy /api/* management:80
|
||||||
|
reverse_proxy /management.ManagementService/* h2c://management:80
|
||||||
|
# Zitadel
|
||||||
|
reverse_proxy /zitadel.admin.v1.AdminService/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /admin/v1/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /zitadel.auth.v1.AuthService/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /auth/v1/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /zitadel.management.v1.ManagementService/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /management/v1/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /zitadel.system.v1.SystemService/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /system/v1/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /assets/v1/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /ui/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /oidc/v1/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /saml/v2/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /oauth/v2/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /.well-known/openid-configuration h2c://zitadel:8080
|
||||||
|
reverse_proxy /openapi/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /debug/* h2c://zitadel:8080
|
||||||
|
# Dashboard
|
||||||
|
reverse_proxy /* dashboard:80
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTurnServerConf() {
|
||||||
|
cat <<EOF
|
||||||
|
listening-port=3478
|
||||||
|
tls-listening-port=5349
|
||||||
|
min-port=$TURN_MIN_PORT
|
||||||
|
max-port=$TURN_MAX_PORT
|
||||||
|
fingerprint
|
||||||
|
lt-cred-mech
|
||||||
|
user=$TURN_USER:$TURN_PASSWORD
|
||||||
|
realm=wiretrustee.com
|
||||||
|
cert=/etc/coturn/certs/cert.pem
|
||||||
|
pkey=/etc/coturn/private/privkey.pem
|
||||||
|
log-file=stdout
|
||||||
|
no-software-attribute
|
||||||
|
pidfile="/var/tmp/turnserver.pid"
|
||||||
|
no-cli
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
renderManagementJson() {
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"Stuns": [
|
||||||
|
{
|
||||||
|
"Proto": "udp",
|
||||||
|
"URI": "stun:$NETBIRD_DOMAIN:3478"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TURNConfig": {
|
||||||
|
"Turns": [
|
||||||
|
{
|
||||||
|
"Proto": "udp",
|
||||||
|
"URI": "turn:$NETBIRD_DOMAIN:3478",
|
||||||
|
"Username": "$TURN_USER",
|
||||||
|
"Password": "$TURN_PASSWORD"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TimeBasedCredentials": false
|
||||||
|
},
|
||||||
|
"Signal": {
|
||||||
|
"Proto": "$NETBIRD_HTTP_PROTOCOL",
|
||||||
|
"URI": "$NETBIRD_DOMAIN:$NETBIRD_PORT"
|
||||||
|
},
|
||||||
|
"HttpConfig": {
|
||||||
|
"AuthIssuer": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN",
|
||||||
|
"AuthAudience": "$NETBIRD_AUTH_CLIENT_ID",
|
||||||
|
"OIDCConfigEndpoint":"$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/.well-known/openid-configuration"
|
||||||
|
},
|
||||||
|
"IdpManagerConfig": {
|
||||||
|
"ManagerType": "zitadel",
|
||||||
|
"ClientConfig": {
|
||||||
|
"Issuer": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT",
|
||||||
|
"TokenEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT/oauth/v2/token",
|
||||||
|
"ClientID": "$NETBIRD_IDP_MGMT_CLIENT_ID",
|
||||||
|
"ClientSecret": "$NETBIRD_IDP_MGMT_CLIENT_SECRET",
|
||||||
|
"GrantType": "client_credentials"
|
||||||
|
},
|
||||||
|
"ExtraConfig": {
|
||||||
|
"ManagementEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT/management/v1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PKCEAuthorizationFlow": {
|
||||||
|
"ProviderConfig": {
|
||||||
|
"Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||||
|
"ClientID": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||||
|
"Scope": "openid profile email offline_access",
|
||||||
|
"RedirectURLs": ["http://localhost:53000/","http://localhost:54000/"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDashboardEnv() {
|
||||||
|
cat <<EOF
|
||||||
|
# Endpoints
|
||||||
|
NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT
|
||||||
|
NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT
|
||||||
|
# OIDC
|
||||||
|
AUTH_AUDIENCE=$NETBIRD_AUTH_CLIENT_ID
|
||||||
|
AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
||||||
|
AUTH_AUTHORITY=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN:$NETBIRD_PORT
|
||||||
|
USE_AUTH0=false
|
||||||
|
AUTH_SUPPORTED_SCOPES="openid profile email offline_access"
|
||||||
|
AUTH_REDIRECT_URI=/nb-auth
|
||||||
|
AUTH_SILENT_REDIRECT_URI=/nb-silent-auth
|
||||||
|
# SSL
|
||||||
|
NGINX_SSL_PORT=443
|
||||||
|
# Letsencrypt
|
||||||
|
LETSENCRYPT_DOMAIN=none
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
renderZitadelEnv() {
|
||||||
|
cat <<EOF
|
||||||
|
ZITADEL_LOG_LEVEL=debug
|
||||||
|
ZITADEL_MASTERKEY=$ZITADEL_MASTERKEY
|
||||||
|
#ZITADEL_DATABASE_COCKROACH_HOST=pgdb
|
||||||
|
#ZITADEL_DATABASE_COCKROACH_USER_USERNAME=zitadel_user
|
||||||
|
#ZITADEL_DATABASE_COCKROACH_USER_SSL_MODE=verify-full
|
||||||
|
#ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT="/pgdb-certs/ca.crt"
|
||||||
|
#ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT="/pgdb-certs/client.zitadel_user.crt"
|
||||||
|
#ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY="/pgdb-certs/client.zitadel_user.key"
|
||||||
|
#ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_MODE=verify-full
|
||||||
|
#ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT="/pgdb-certs/ca.crt"
|
||||||
|
#ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT="/pgdb-certs/client.root.crt"
|
||||||
|
#ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY="/pgdb-certs/client.root.key"
|
||||||
|
ZITADEL_DATABASE_POSTGRES_HOST=pgdb
|
||||||
|
ZITADEL_DATABASE_POSTGRES_PORT=5432
|
||||||
|
ZITADEL_DATABASE_POSTGRES_DATABASE=zitadeldb
|
||||||
|
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME=zitadeladmin
|
||||||
|
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD=zitadeladmin
|
||||||
|
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE=disable
|
||||||
|
ZITADEL_DATABASE_POSTGRES_USER_USERNAME=zitadeluser
|
||||||
|
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD=zitadeluser
|
||||||
|
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE=disable
|
||||||
|
ZITADEL_EXTERNALSECURE=$ZITADEL_EXTERNALSECURE
|
||||||
|
ZITADEL_TLS_ENABLED="false"
|
||||||
|
ZITADEL_EXTERNALPORT=$NETBIRD_PORT
|
||||||
|
ZITADEL_EXTERNALDOMAIN=$NETBIRD_DOMAIN
|
||||||
|
ZITADEL_FIRSTINSTANCE_PATPATH=/machinekey/zitadel-admin-sa.token
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME=zitadel-admin-sa
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME=Admin
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_SCOPES=openid
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE=$ZIDATE_TOKEN_EXPIRATION_DATE
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDockerCompose() {
|
||||||
|
cat <<EOF
|
||||||
|
version: "3.4"
|
||||||
|
services:
|
||||||
|
# Caddy reverse proxy
|
||||||
|
caddy:
|
||||||
|
image: caddy
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [ netbird ]
|
||||||
|
ports:
|
||||||
|
- '443:443'
|
||||||
|
- '80:80'
|
||||||
|
- '8080:8080'
|
||||||
|
volumes:
|
||||||
|
- netbird_caddy_data:/data
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile
|
||||||
|
#UI dashboard
|
||||||
|
dashboard:
|
||||||
|
image: wiretrustee/dashboard:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [netbird]
|
||||||
|
env_file:
|
||||||
|
- ./dashboard.env
|
||||||
|
# Signal
|
||||||
|
signal:
|
||||||
|
image: netbirdio/signal:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [netbird]
|
||||||
|
# Management
|
||||||
|
management:
|
||||||
|
image: netbirdio/management:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [netbird]
|
||||||
|
volumes:
|
||||||
|
- netbird_management:/var/lib/netbird
|
||||||
|
- ./management.json:/etc/netbird/management.json
|
||||||
|
command: [
|
||||||
|
"--port", "80",
|
||||||
|
"--log-file", "console",
|
||||||
|
"--log-level", "debug",
|
||||||
|
"--disable-anonymous-metrics=false",
|
||||||
|
"--single-account-mode-domain=netbird.selfhosted",
|
||||||
|
"--dns-domain=netbird.selfhosted",
|
||||||
|
]
|
||||||
|
# Coturn, AKA relay server
|
||||||
|
coturn:
|
||||||
|
image: coturn/coturn
|
||||||
|
restart: unless-stopped
|
||||||
|
domainname: netbird.relay.selfhosted
|
||||||
|
volumes:
|
||||||
|
- ./turnserver.conf:/etc/turnserver.conf:ro
|
||||||
|
network_mode: host
|
||||||
|
command:
|
||||||
|
- -c /etc/turnserver.conf
|
||||||
|
# Zitadel - identity provider
|
||||||
|
zitadel:
|
||||||
|
restart: 'always'
|
||||||
|
networks: [netbird]
|
||||||
|
image: 'ghcr.io/zitadel/zitadel:v2.31.3'
|
||||||
|
command: 'start-from-init --masterkeyFromEnv --tlsMode $ZITADEL_TLS_MODE'
|
||||||
|
env_file:
|
||||||
|
- ./zitadel.env
|
||||||
|
depends_on:
|
||||||
|
pgdb:
|
||||||
|
condition: 'service_healthy'
|
||||||
|
volumes:
|
||||||
|
- ./machinekey:/machinekey
|
||||||
|
- netbird_zitadel_certs:/pgdb-certs:ro
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "curl", "-f", "http://localhost:8080/debug/healthz" ]
|
||||||
|
interval: '10s'
|
||||||
|
timeout: '30s'
|
||||||
|
retries: 5
|
||||||
|
start_period: '20s'
|
||||||
|
# CockroachDB for zitadel
|
||||||
|
pgdb:
|
||||||
|
restart: 'always'
|
||||||
|
networks: [netbird]
|
||||||
|
image: 'postgres:15'
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=zitadeladmin
|
||||||
|
- POSTGRES_PASSWORD=zitadeladmin
|
||||||
|
- POSTGRES_DB=zitadeldb
|
||||||
|
#command: 'start-single-node --advertise-addr pgdb'
|
||||||
|
volumes:
|
||||||
|
- netbird_pgdb_data:/cockroach/cockroach-data
|
||||||
|
- netbird_pgdb_certs:/cockroach/certs
|
||||||
|
- netbird_zitadel_certs:/zitadel-certs
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: '10s'
|
||||||
|
timeout: '30s'
|
||||||
|
retries: 5
|
||||||
|
start_period: '20s'
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
netbird_management:
|
||||||
|
netbird_caddy_data:
|
||||||
|
netbird_pgdb_data:
|
||||||
|
netbird_pgdb_certs:
|
||||||
|
netbird_zitadel_certs:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
netbird:
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
initEnvironment
|
||||||
@@ -59,5 +59,17 @@
|
|||||||
"Scope": "$NETBIRD_AUTH_DEVICE_AUTH_SCOPE",
|
"Scope": "$NETBIRD_AUTH_DEVICE_AUTH_SCOPE",
|
||||||
"UseIDToken": $NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN
|
"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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
|||||||
NETBIRD_AUTH_DEVICE_AUTH_SCOPE="openid"
|
NETBIRD_AUTH_DEVICE_AUTH_SCOPE="openid"
|
||||||
NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN=false
|
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
|
# IDP Management
|
||||||
# -------------------------------------------
|
# -------------------------------------------
|
||||||
# eg. zitadel, auth0, azure, keycloak
|
# eg. zitadel, auth0, azure, keycloak
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
request_jwt_token() {
|
|
||||||
INSTANCE_URL=$1
|
|
||||||
BODY="grant_type=client_credentials&scope=urn:zitadel:iam:org:project:id:zitadel:aud&client_id=$ZITADEL_CLIENT_ID&client_secret=$ZITADEL_CLIENT_SECRET"
|
|
||||||
|
|
||||||
RESPONSE=$(
|
|
||||||
curl -X POST "$INSTANCE_URL/oauth/v2/token" \
|
|
||||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
||||||
-d "$BODY"
|
|
||||||
)
|
|
||||||
echo "$RESPONSE" | jq -r '.access_token'
|
|
||||||
}
|
|
||||||
|
|
||||||
create_new_project() {
|
|
||||||
INSTANCE_URL=$1
|
|
||||||
ACCESS_TOKEN=$2
|
|
||||||
PROJECT_NAME="NETBIRD"
|
|
||||||
|
|
||||||
RESPONSE=$(
|
|
||||||
curl -X POST "$INSTANCE_URL/management/v1/projects" \
|
|
||||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"name": "'"$PROJECT_NAME"'"}'
|
|
||||||
)
|
|
||||||
echo "$RESPONSE" | jq -r '.id'
|
|
||||||
}
|
|
||||||
|
|
||||||
create_new_application() {
|
|
||||||
INSTANCE_URL=$1
|
|
||||||
ACCESS_TOKEN=$2
|
|
||||||
APPLICATION_NAME="netbird"
|
|
||||||
|
|
||||||
RESPONSE=$(
|
|
||||||
curl -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \
|
|
||||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"name": "'"$APPLICATION_NAME"'",
|
|
||||||
"redirectUris": [
|
|
||||||
"'"$BASE_REDIRECT_URL"'/auth"
|
|
||||||
],
|
|
||||||
"RESPONSETypes": [
|
|
||||||
"OIDC_RESPONSE_TYPE_CODE"
|
|
||||||
],
|
|
||||||
"grantTypes": [
|
|
||||||
"OIDC_GRANT_TYPE_AUTHORIZATION_CODE",
|
|
||||||
"OIDC_GRANT_TYPE_REFRESH_TOKEN"
|
|
||||||
],
|
|
||||||
"appType": "OIDC_APP_TYPE_USER_AGENT",
|
|
||||||
"authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE",
|
|
||||||
"postLogoutRedirectUris": [
|
|
||||||
"'"$BASE_REDIRECT_URL"'/silent-auth"
|
|
||||||
],
|
|
||||||
"version": "OIDC_VERSION_1_0",
|
|
||||||
"devMode": '"$ZITADEL_DEV_MODE"',
|
|
||||||
"accessTokenType": "OIDC_TOKEN_TYPE_JWT",
|
|
||||||
"accessTokenRoleAssertion": true,
|
|
||||||
"skipNativeAppSuccessPage": true
|
|
||||||
}'
|
|
||||||
)
|
|
||||||
echo "$RESPONSE" | jq -r '.clientId'
|
|
||||||
}
|
|
||||||
|
|
||||||
configure_zitadel_instance() {
|
|
||||||
# extract zitadel instance url
|
|
||||||
INSTANCE_URL=$(echo "$NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT" | sed 's/\/\.well-known\/openid-configuration//')
|
|
||||||
DOC_URL="https://netbird.io/docs/integrations/identity-providers/self-hosted/using-netbird-with-zitadel#step-4-create-a-service-user"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
printf "configuring zitadel instance: $INSTANCE_URL \n \
|
|
||||||
before proceeding, please create a new service account for authorization by following the instructions (step 4 and 5
|
|
||||||
) in the documentation at %s\n" "$DOC_URL"
|
|
||||||
echo "Please ensure that the new service account has 'Org Owner' permission in order for this to work."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
read -n 1 -s -r -p "press any key to continue..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# prompt the user to enter service account clientID
|
|
||||||
echo ""
|
|
||||||
read -r -p "enter service account ClientId: " ZITADEL_CLIENT_ID
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Prompt the user to enter service account clientSecret
|
|
||||||
read -r -p "enter service account ClientSecret: " ZITADEL_CLIENT_SECRET
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# get an access token from zitadel
|
|
||||||
echo "retrieving access token from zitadel"
|
|
||||||
ACCESS_TOKEN=$(request_jwt_token "$INSTANCE_URL")
|
|
||||||
if [ "$ACCESS_TOKEN" = "null" ]; then
|
|
||||||
echo "failed requesting access token"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# create the zitadel project
|
|
||||||
echo "creating new zitadel project"
|
|
||||||
PROJECT_ID=$(create_new_project "$INSTANCE_URL" "$ACCESS_TOKEN")
|
|
||||||
if [ "$PROJECT_ID" = "null" ]; then
|
|
||||||
echo "failed creating new zitadel project"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ZITADEL_DEV_MODE=false
|
|
||||||
if [[ $NETBIRD_DOMAIN == *"localhost"* ]]; then
|
|
||||||
BASE_REDIRECT_URL="http://$NETBIRD_DOMAIN"
|
|
||||||
ZITADEL_DEV_MODE=true
|
|
||||||
else
|
|
||||||
BASE_REDIRECT_URL="https://$NETBIRD_DOMAIN"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# create zitadel spa application
|
|
||||||
echo "creating new zitadel spa application"
|
|
||||||
APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$ACCESS_TOKEN")
|
|
||||||
if [ "$APPLICATION_CLIENT_ID" = "null" ]; then
|
|
||||||
echo "failed creating new zitadel spa application"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
@@ -15,5 +15,6 @@ type Client interface {
|
|||||||
Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
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)
|
Login(serverKey wgtypes.Key, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||||
GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
||||||
|
GetPKCEAuthorizationFlow(serverKey wgtypes.Key) (*proto.PKCEAuthorizationFlow, error)
|
||||||
GetNetworkMap() (*proto.NetworkMap, error)
|
GetNetworkMap() (*proto.NetworkMap, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -400,3 +400,49 @@ func Test_GetDeviceAuthorizationFlow(t *testing.T) {
|
|||||||
assert.Equal(t, expectedFlowInfo.Provider, flowInfo.Provider, "provider should match")
|
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")
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -366,6 +366,40 @@ func (c *GrpcClient) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.D
|
|||||||
return flowInfoResp, nil
|
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() {
|
func (c *GrpcClient) notifyDisconnected() {
|
||||||
c.connStateCallbackLock.RLock()
|
c.connStateCallbackLock.RLock()
|
||||||
defer c.connStateCallbackLock.RUnlock()
|
defer c.connStateCallbackLock.RUnlock()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type MockClient struct {
|
|||||||
RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
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)
|
LoginFunc func(serverKey wgtypes.Key, info *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||||
GetDeviceAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
GetDeviceAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
||||||
|
GetPKCEAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.PKCEAuthorizationFlow, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockClient) Close() error {
|
func (m *MockClient) Close() error {
|
||||||
@@ -57,6 +58,13 @@ func (m *MockClient) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.D
|
|||||||
return m.GetDeviceAuthorizationFlowFunc(serverKey)
|
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
|
// GetNetworkMap mock implementation of GetNetworkMap from mgm.Client interface
|
||||||
func (m *MockClient) GetNetworkMap() (*proto.NetworkMap, error) {
|
func (m *MockClient) GetNetworkMap() (*proto.NetworkMap, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -426,6 +426,15 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
|||||||
config.DeviceAuthorizationFlow.ProviderConfig.Scope = server.DefaultDeviceAuthFlowScope
|
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
|
return config, err
|
||||||
@@ -433,10 +442,11 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
|||||||
|
|
||||||
// OIDCConfigResponse used for parsing OIDC config response
|
// OIDCConfigResponse used for parsing OIDC config response
|
||||||
type OIDCConfigResponse struct {
|
type OIDCConfigResponse struct {
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
TokenEndpoint string `json:"token_endpoint"`
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
DeviceAuthEndpoint string `json:"device_authorization_endpoint"`
|
DeviceAuthEndpoint string `json:"device_authorization_endpoint"`
|
||||||
JwksURI string `json:"jwks_uri"`
|
JwksURI string `json:"jwks_uri"`
|
||||||
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchOIDCConfig fetches OIDC configuration from the IDP
|
// fetchOIDCConfig fetches OIDC configuration from the IDP
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.21.9
|
// protoc v3.21.12
|
||||||
// source: management.proto
|
// source: management.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -162,7 +162,7 @@ func (x FirewallRuleDirection) Number() protoreflect.EnumNumber {
|
|||||||
|
|
||||||
// Deprecated: Use FirewallRuleDirection.Descriptor instead.
|
// Deprecated: Use FirewallRuleDirection.Descriptor instead.
|
||||||
func (FirewallRuleDirection) EnumDescriptor() ([]byte, []int) {
|
func (FirewallRuleDirection) EnumDescriptor() ([]byte, []int) {
|
||||||
return file_management_proto_rawDescGZIP(), []int{25, 0}
|
return file_management_proto_rawDescGZIP(), []int{27, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallRuleAction int32
|
type FirewallRuleAction int32
|
||||||
@@ -208,7 +208,7 @@ func (x FirewallRuleAction) Number() protoreflect.EnumNumber {
|
|||||||
|
|
||||||
// Deprecated: Use FirewallRuleAction.Descriptor instead.
|
// Deprecated: Use FirewallRuleAction.Descriptor instead.
|
||||||
func (FirewallRuleAction) EnumDescriptor() ([]byte, []int) {
|
func (FirewallRuleAction) EnumDescriptor() ([]byte, []int) {
|
||||||
return file_management_proto_rawDescGZIP(), []int{25, 1}
|
return file_management_proto_rawDescGZIP(), []int{27, 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirewallRuleProtocol int32
|
type FirewallRuleProtocol int32
|
||||||
@@ -263,7 +263,7 @@ func (x FirewallRuleProtocol) Number() protoreflect.EnumNumber {
|
|||||||
|
|
||||||
// Deprecated: Use FirewallRuleProtocol.Descriptor instead.
|
// Deprecated: Use FirewallRuleProtocol.Descriptor instead.
|
||||||
func (FirewallRuleProtocol) EnumDescriptor() ([]byte, []int) {
|
func (FirewallRuleProtocol) EnumDescriptor() ([]byte, []int) {
|
||||||
return file_management_proto_rawDescGZIP(), []int{25, 2}
|
return file_management_proto_rawDescGZIP(), []int{27, 2}
|
||||||
}
|
}
|
||||||
|
|
||||||
type EncryptedMessage struct {
|
type EncryptedMessage struct {
|
||||||
@@ -1477,7 +1477,96 @@ func (x *DeviceAuthorizationFlow) GetProviderConfig() *ProviderConfig {
|
|||||||
return nil
|
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 {
|
type ProviderConfig struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -1500,12 +1589,16 @@ type ProviderConfig struct {
|
|||||||
Scope string `protobuf:"bytes,7,opt,name=Scope,proto3" json:"Scope,omitempty"`
|
Scope string `protobuf:"bytes,7,opt,name=Scope,proto3" json:"Scope,omitempty"`
|
||||||
// UseIDToken indicates if the id token should be used for authentication
|
// UseIDToken indicates if the id token should be used for authentication
|
||||||
UseIDToken bool `protobuf:"varint,8,opt,name=UseIDToken,proto3" json:"UseIDToken,omitempty"`
|
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() {
|
func (x *ProviderConfig) Reset() {
|
||||||
*x = ProviderConfig{}
|
*x = ProviderConfig{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_management_proto_msgTypes[18]
|
mi := &file_management_proto_msgTypes[20]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -1518,7 +1611,7 @@ func (x *ProviderConfig) String() string {
|
|||||||
func (*ProviderConfig) ProtoMessage() {}
|
func (*ProviderConfig) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ProviderConfig) ProtoReflect() protoreflect.Message {
|
func (x *ProviderConfig) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_management_proto_msgTypes[18]
|
mi := &file_management_proto_msgTypes[20]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -1531,7 +1624,7 @@ func (x *ProviderConfig) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ProviderConfig.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ProviderConfig.ProtoReflect.Descriptor instead.
|
||||||
func (*ProviderConfig) Descriptor() ([]byte, []int) {
|
func (*ProviderConfig) Descriptor() ([]byte, []int) {
|
||||||
return file_management_proto_rawDescGZIP(), []int{18}
|
return file_management_proto_rawDescGZIP(), []int{20}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ProviderConfig) GetClientID() string {
|
func (x *ProviderConfig) GetClientID() string {
|
||||||
@@ -1590,6 +1683,20 @@ func (x *ProviderConfig) GetUseIDToken() bool {
|
|||||||
return false
|
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
|
// Route represents a route.Route object
|
||||||
type Route struct {
|
type Route struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
@@ -1608,7 +1715,7 @@ type Route struct {
|
|||||||
func (x *Route) Reset() {
|
func (x *Route) Reset() {
|
||||||
*x = Route{}
|
*x = Route{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_management_proto_msgTypes[19]
|
mi := &file_management_proto_msgTypes[21]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -1621,7 +1728,7 @@ func (x *Route) String() string {
|
|||||||
func (*Route) ProtoMessage() {}
|
func (*Route) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Route) ProtoReflect() protoreflect.Message {
|
func (x *Route) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_management_proto_msgTypes[19]
|
mi := &file_management_proto_msgTypes[21]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -1634,7 +1741,7 @@ func (x *Route) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Route.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Route.ProtoReflect.Descriptor instead.
|
||||||
func (*Route) Descriptor() ([]byte, []int) {
|
func (*Route) Descriptor() ([]byte, []int) {
|
||||||
return file_management_proto_rawDescGZIP(), []int{19}
|
return file_management_proto_rawDescGZIP(), []int{21}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Route) GetID() string {
|
func (x *Route) GetID() string {
|
||||||
@@ -1700,7 +1807,7 @@ type DNSConfig struct {
|
|||||||
func (x *DNSConfig) Reset() {
|
func (x *DNSConfig) Reset() {
|
||||||
*x = DNSConfig{}
|
*x = DNSConfig{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_management_proto_msgTypes[20]
|
mi := &file_management_proto_msgTypes[22]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -1713,7 +1820,7 @@ func (x *DNSConfig) String() string {
|
|||||||
func (*DNSConfig) ProtoMessage() {}
|
func (*DNSConfig) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *DNSConfig) ProtoReflect() protoreflect.Message {
|
func (x *DNSConfig) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_management_proto_msgTypes[20]
|
mi := &file_management_proto_msgTypes[22]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -1726,7 +1833,7 @@ func (x *DNSConfig) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use DNSConfig.ProtoReflect.Descriptor instead.
|
// Deprecated: Use DNSConfig.ProtoReflect.Descriptor instead.
|
||||||
func (*DNSConfig) Descriptor() ([]byte, []int) {
|
func (*DNSConfig) Descriptor() ([]byte, []int) {
|
||||||
return file_management_proto_rawDescGZIP(), []int{20}
|
return file_management_proto_rawDescGZIP(), []int{22}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *DNSConfig) GetServiceEnable() bool {
|
func (x *DNSConfig) GetServiceEnable() bool {
|
||||||
@@ -1763,7 +1870,7 @@ type CustomZone struct {
|
|||||||
func (x *CustomZone) Reset() {
|
func (x *CustomZone) Reset() {
|
||||||
*x = CustomZone{}
|
*x = CustomZone{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_management_proto_msgTypes[21]
|
mi := &file_management_proto_msgTypes[23]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -1776,7 +1883,7 @@ func (x *CustomZone) String() string {
|
|||||||
func (*CustomZone) ProtoMessage() {}
|
func (*CustomZone) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *CustomZone) ProtoReflect() protoreflect.Message {
|
func (x *CustomZone) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_management_proto_msgTypes[21]
|
mi := &file_management_proto_msgTypes[23]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -1789,7 +1896,7 @@ func (x *CustomZone) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use CustomZone.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CustomZone.ProtoReflect.Descriptor instead.
|
||||||
func (*CustomZone) Descriptor() ([]byte, []int) {
|
func (*CustomZone) Descriptor() ([]byte, []int) {
|
||||||
return file_management_proto_rawDescGZIP(), []int{21}
|
return file_management_proto_rawDescGZIP(), []int{23}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CustomZone) GetDomain() string {
|
func (x *CustomZone) GetDomain() string {
|
||||||
@@ -1822,7 +1929,7 @@ type SimpleRecord struct {
|
|||||||
func (x *SimpleRecord) Reset() {
|
func (x *SimpleRecord) Reset() {
|
||||||
*x = SimpleRecord{}
|
*x = SimpleRecord{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_management_proto_msgTypes[22]
|
mi := &file_management_proto_msgTypes[24]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -1835,7 +1942,7 @@ func (x *SimpleRecord) String() string {
|
|||||||
func (*SimpleRecord) ProtoMessage() {}
|
func (*SimpleRecord) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *SimpleRecord) ProtoReflect() protoreflect.Message {
|
func (x *SimpleRecord) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_management_proto_msgTypes[22]
|
mi := &file_management_proto_msgTypes[24]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -1848,7 +1955,7 @@ func (x *SimpleRecord) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use SimpleRecord.ProtoReflect.Descriptor instead.
|
// Deprecated: Use SimpleRecord.ProtoReflect.Descriptor instead.
|
||||||
func (*SimpleRecord) Descriptor() ([]byte, []int) {
|
func (*SimpleRecord) Descriptor() ([]byte, []int) {
|
||||||
return file_management_proto_rawDescGZIP(), []int{22}
|
return file_management_proto_rawDescGZIP(), []int{24}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SimpleRecord) GetName() string {
|
func (x *SimpleRecord) GetName() string {
|
||||||
@@ -1900,7 +2007,7 @@ type NameServerGroup struct {
|
|||||||
func (x *NameServerGroup) Reset() {
|
func (x *NameServerGroup) Reset() {
|
||||||
*x = NameServerGroup{}
|
*x = NameServerGroup{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_management_proto_msgTypes[23]
|
mi := &file_management_proto_msgTypes[25]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -1913,7 +2020,7 @@ func (x *NameServerGroup) String() string {
|
|||||||
func (*NameServerGroup) ProtoMessage() {}
|
func (*NameServerGroup) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *NameServerGroup) ProtoReflect() protoreflect.Message {
|
func (x *NameServerGroup) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_management_proto_msgTypes[23]
|
mi := &file_management_proto_msgTypes[25]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -1926,7 +2033,7 @@ func (x *NameServerGroup) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use NameServerGroup.ProtoReflect.Descriptor instead.
|
// Deprecated: Use NameServerGroup.ProtoReflect.Descriptor instead.
|
||||||
func (*NameServerGroup) Descriptor() ([]byte, []int) {
|
func (*NameServerGroup) Descriptor() ([]byte, []int) {
|
||||||
return file_management_proto_rawDescGZIP(), []int{23}
|
return file_management_proto_rawDescGZIP(), []int{25}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NameServerGroup) GetNameServers() []*NameServer {
|
func (x *NameServerGroup) GetNameServers() []*NameServer {
|
||||||
@@ -1964,7 +2071,7 @@ type NameServer struct {
|
|||||||
func (x *NameServer) Reset() {
|
func (x *NameServer) Reset() {
|
||||||
*x = NameServer{}
|
*x = NameServer{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_management_proto_msgTypes[24]
|
mi := &file_management_proto_msgTypes[26]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -1977,7 +2084,7 @@ func (x *NameServer) String() string {
|
|||||||
func (*NameServer) ProtoMessage() {}
|
func (*NameServer) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *NameServer) ProtoReflect() protoreflect.Message {
|
func (x *NameServer) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_management_proto_msgTypes[24]
|
mi := &file_management_proto_msgTypes[26]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -1990,7 +2097,7 @@ func (x *NameServer) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use NameServer.ProtoReflect.Descriptor instead.
|
// Deprecated: Use NameServer.ProtoReflect.Descriptor instead.
|
||||||
func (*NameServer) Descriptor() ([]byte, []int) {
|
func (*NameServer) Descriptor() ([]byte, []int) {
|
||||||
return file_management_proto_rawDescGZIP(), []int{24}
|
return file_management_proto_rawDescGZIP(), []int{26}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NameServer) GetIP() string {
|
func (x *NameServer) GetIP() string {
|
||||||
@@ -2030,7 +2137,7 @@ type FirewallRule struct {
|
|||||||
func (x *FirewallRule) Reset() {
|
func (x *FirewallRule) Reset() {
|
||||||
*x = FirewallRule{}
|
*x = FirewallRule{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_management_proto_msgTypes[25]
|
mi := &file_management_proto_msgTypes[27]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -2043,7 +2150,7 @@ func (x *FirewallRule) String() string {
|
|||||||
func (*FirewallRule) ProtoMessage() {}
|
func (*FirewallRule) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *FirewallRule) ProtoReflect() protoreflect.Message {
|
func (x *FirewallRule) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_management_proto_msgTypes[25]
|
mi := &file_management_proto_msgTypes[27]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -2056,7 +2163,7 @@ func (x *FirewallRule) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use FirewallRule.ProtoReflect.Descriptor instead.
|
// Deprecated: Use FirewallRule.ProtoReflect.Descriptor instead.
|
||||||
func (*FirewallRule) Descriptor() ([]byte, []int) {
|
func (*FirewallRule) Descriptor() ([]byte, []int) {
|
||||||
return file_management_proto_rawDescGZIP(), []int{25}
|
return file_management_proto_rawDescGZIP(), []int{27}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FirewallRule) GetPeerIP() string {
|
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, 0x52, 0x0e,
|
||||||
0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x16,
|
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,
|
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,
|
0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75,
|
||||||
0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69,
|
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52,
|
||||||
0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5b, 0x0a, 0x15, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75,
|
||||||
0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53,
|
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12,
|
||||||
0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x69,
|
0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d,
|
0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e,
|
||||||
0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20,
|
0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a,
|
0x66, 0x69, 0x67, 0x22, 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
|
||||||
0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f,
|
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||||
0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63,
|
0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||||
0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a,
|
0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72,
|
||||||
0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06,
|
0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f,
|
0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||||
0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01,
|
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a,
|
||||||
0x28, 0x09, 0x52, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x55, 0x73, 0x65,
|
0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x55,
|
0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x44, 0x65,
|
||||||
0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f,
|
0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
||||||
0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75,
|
||||||
0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02,
|
0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x6f,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a,
|
0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||||
0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01,
|
0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
||||||
0x28, 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12,
|
0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50,
|
0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x49, 0x44, 0x54,
|
||||||
0x65, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20,
|
0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x55, 0x73, 0x65, 0x49,
|
||||||
0x01, 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d,
|
0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
|
||||||
0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52,
|
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
|
||||||
0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e,
|
0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
|
||||||
0x65, 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49,
|
0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c,
|
||||||
0x44, 0x22, 0xb4, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x18, 0x0a, 0x20, 0x03,
|
||||||
0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45,
|
0x22, 0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44,
|
||||||
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65,
|
||||||
0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74,
|
||||||
0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d,
|
0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54,
|
||||||
0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61,
|
0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f,
|
||||||
0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38,
|
0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x04,
|
||||||
0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65,
|
||||||
0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x72,
|
||||||
0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73,
|
0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65,
|
||||||
0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74,
|
0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61,
|
||||||
0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32,
|
0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x22, 0xb4, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53,
|
||||||
0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||||
0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69, 0x6d,
|
0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x53,
|
||||||
0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72,
|
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x10,
|
||||||
0x64, 0x73, 0x22, 0x74, 0x0a, 0x0c, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f,
|
0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73,
|
||||||
0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02,
|
0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72,
|
||||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6c,
|
0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47,
|
||||||
0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73,
|
0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a,
|
||||||
0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54,
|
0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e,
|
||||||
0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28,
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f,
|
||||||
0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22, 0x7f, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65,
|
0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x22,
|
||||||
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e,
|
0x58, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a,
|
||||||
0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
|
0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44,
|
||||||
0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61,
|
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73,
|
||||||
0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65,
|
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,
|
0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12,
|
0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x74, 0x0a, 0x0c, 0x53, 0x69, 0x6d,
|
||||||
0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
|
0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d,
|
||||||
0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d,
|
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a,
|
||||||
0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20,
|
0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x54, 0x79, 0x70,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70,
|
0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12,
|
0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04,
|
||||||
0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50,
|
0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61,
|
||||||
0x6f, 0x72, 0x74, 0x22, 0xf0, 0x02, 0x0a, 0x0c, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c,
|
0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22,
|
||||||
0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01,
|
0x7f, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09,
|
0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||||
0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72,
|
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52,
|
||||||
0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
|
0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07,
|
||||||
0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37,
|
0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50,
|
||||||
0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f,
|
0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65,
|
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73,
|
||||||
0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0e,
|
||||||
0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f,
|
0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16,
|
||||||
0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52,
|
0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03,
|
||||||
0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72,
|
0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xf0, 0x02, 0x0a, 0x0c, 0x46,
|
||||||
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05,
|
0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69,
|
0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65,
|
||||||
0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12,
|
0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69,
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08,
|
0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65,
|
||||||
0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74,
|
0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65,
|
||||||
0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
|
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18,
|
||||||
0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43,
|
0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||||
0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04,
|
0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e,
|
||||||
0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x32, 0xf7, 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67,
|
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d,
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05,
|
0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69,
|
||||||
0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a,
|
||||||
0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72,
|
||||||
0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61,
|
0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06,
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
|
0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x22,
|
||||||
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43,
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
|
0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x22,
|
||||||
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47,
|
0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55,
|
||||||
0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61,
|
0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10,
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d,
|
0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44,
|
||||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76,
|
0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x32, 0xd1, 0x03,
|
||||||
0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
|
0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76,
|
||||||
0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d,
|
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, 0x6d, 0x70, 0x74, 0x79, 0x1a,
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
||||||
0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70,
|
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e,
|
||||||
0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63,
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
||||||
0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c,
|
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79,
|
||||||
0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
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,
|
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,
|
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,
|
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,
|
0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b,
|
||||||
0x6f, 0x33,
|
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 (
|
var (
|
||||||
@@ -2400,7 +2526,7 @@ func file_management_proto_rawDescGZIP() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
|
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{}{
|
var file_management_proto_goTypes = []interface{}{
|
||||||
(HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol
|
(HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol
|
||||||
(DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider
|
(DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider
|
||||||
@@ -2425,15 +2551,17 @@ var file_management_proto_goTypes = []interface{}{
|
|||||||
(*SSHConfig)(nil), // 20: management.SSHConfig
|
(*SSHConfig)(nil), // 20: management.SSHConfig
|
||||||
(*DeviceAuthorizationFlowRequest)(nil), // 21: management.DeviceAuthorizationFlowRequest
|
(*DeviceAuthorizationFlowRequest)(nil), // 21: management.DeviceAuthorizationFlowRequest
|
||||||
(*DeviceAuthorizationFlow)(nil), // 22: management.DeviceAuthorizationFlow
|
(*DeviceAuthorizationFlow)(nil), // 22: management.DeviceAuthorizationFlow
|
||||||
(*ProviderConfig)(nil), // 23: management.ProviderConfig
|
(*PKCEAuthorizationFlowRequest)(nil), // 23: management.PKCEAuthorizationFlowRequest
|
||||||
(*Route)(nil), // 24: management.Route
|
(*PKCEAuthorizationFlow)(nil), // 24: management.PKCEAuthorizationFlow
|
||||||
(*DNSConfig)(nil), // 25: management.DNSConfig
|
(*ProviderConfig)(nil), // 25: management.ProviderConfig
|
||||||
(*CustomZone)(nil), // 26: management.CustomZone
|
(*Route)(nil), // 26: management.Route
|
||||||
(*SimpleRecord)(nil), // 27: management.SimpleRecord
|
(*DNSConfig)(nil), // 27: management.DNSConfig
|
||||||
(*NameServerGroup)(nil), // 28: management.NameServerGroup
|
(*CustomZone)(nil), // 28: management.CustomZone
|
||||||
(*NameServer)(nil), // 29: management.NameServer
|
(*SimpleRecord)(nil), // 29: management.SimpleRecord
|
||||||
(*FirewallRule)(nil), // 30: management.FirewallRule
|
(*NameServerGroup)(nil), // 30: management.NameServerGroup
|
||||||
(*timestamppb.Timestamp)(nil), // 31: google.protobuf.Timestamp
|
(*NameServer)(nil), // 31: management.NameServer
|
||||||
|
(*FirewallRule)(nil), // 32: management.FirewallRule
|
||||||
|
(*timestamppb.Timestamp)(nil), // 33: google.protobuf.Timestamp
|
||||||
}
|
}
|
||||||
var file_management_proto_depIdxs = []int32{
|
var file_management_proto_depIdxs = []int32{
|
||||||
14, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
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
|
9, // 5: management.LoginRequest.peerKeys:type_name -> management.PeerKeys
|
||||||
14, // 6: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
14, // 6: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
||||||
17, // 7: management.LoginResponse.peerConfig:type_name -> management.PeerConfig
|
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
|
15, // 9: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig
|
||||||
16, // 10: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig
|
16, // 10: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig
|
||||||
15, // 11: management.WiretrusteeConfig.signal:type_name -> management.HostConfig
|
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
|
20, // 14: management.PeerConfig.sshConfig:type_name -> management.SSHConfig
|
||||||
17, // 15: management.NetworkMap.peerConfig:type_name -> management.PeerConfig
|
17, // 15: management.NetworkMap.peerConfig:type_name -> management.PeerConfig
|
||||||
19, // 16: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig
|
19, // 16: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig
|
||||||
24, // 17: management.NetworkMap.Routes:type_name -> management.Route
|
26, // 17: management.NetworkMap.Routes:type_name -> management.Route
|
||||||
25, // 18: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig
|
27, // 18: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig
|
||||||
19, // 19: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig
|
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
|
20, // 21: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig
|
||||||
1, // 22: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider
|
1, // 22: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider
|
||||||
23, // 23: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
|
25, // 23: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
|
||||||
28, // 24: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup
|
25, // 24: management.PKCEAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
|
||||||
26, // 25: management.DNSConfig.CustomZones:type_name -> management.CustomZone
|
30, // 25: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup
|
||||||
27, // 26: management.CustomZone.Records:type_name -> management.SimpleRecord
|
28, // 26: management.DNSConfig.CustomZones:type_name -> management.CustomZone
|
||||||
29, // 27: management.NameServerGroup.NameServers:type_name -> management.NameServer
|
29, // 27: management.CustomZone.Records:type_name -> management.SimpleRecord
|
||||||
2, // 28: management.FirewallRule.Direction:type_name -> management.FirewallRule.direction
|
31, // 28: management.NameServerGroup.NameServers:type_name -> management.NameServer
|
||||||
3, // 29: management.FirewallRule.Action:type_name -> management.FirewallRule.action
|
2, // 29: management.FirewallRule.Direction:type_name -> management.FirewallRule.direction
|
||||||
4, // 30: management.FirewallRule.Protocol:type_name -> management.FirewallRule.protocol
|
3, // 30: management.FirewallRule.Action:type_name -> management.FirewallRule.action
|
||||||
5, // 31: management.ManagementService.Login:input_type -> management.EncryptedMessage
|
4, // 31: management.FirewallRule.Protocol:type_name -> management.FirewallRule.protocol
|
||||||
5, // 32: management.ManagementService.Sync:input_type -> management.EncryptedMessage
|
5, // 32: management.ManagementService.Login:input_type -> management.EncryptedMessage
|
||||||
13, // 33: management.ManagementService.GetServerKey:input_type -> management.Empty
|
5, // 33: management.ManagementService.Sync:input_type -> management.EncryptedMessage
|
||||||
13, // 34: management.ManagementService.isHealthy:input_type -> management.Empty
|
13, // 34: management.ManagementService.GetServerKey:input_type -> management.Empty
|
||||||
5, // 35: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
|
13, // 35: management.ManagementService.isHealthy:input_type -> management.Empty
|
||||||
5, // 36: management.ManagementService.Login:output_type -> management.EncryptedMessage
|
5, // 36: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
|
||||||
5, // 37: management.ManagementService.Sync:output_type -> management.EncryptedMessage
|
5, // 37: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage
|
||||||
12, // 38: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
|
5, // 38: management.ManagementService.Login:output_type -> management.EncryptedMessage
|
||||||
13, // 39: management.ManagementService.isHealthy:output_type -> management.Empty
|
5, // 39: management.ManagementService.Sync:output_type -> management.EncryptedMessage
|
||||||
5, // 40: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
|
12, // 40: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
|
||||||
36, // [36:41] is the sub-list for method output_type
|
13, // 41: management.ManagementService.isHealthy:output_type -> management.Empty
|
||||||
31, // [31:36] is the sub-list for method input_type
|
5, // 42: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
|
||||||
31, // [31:31] is the sub-list for extension type_name
|
5, // 43: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage
|
||||||
31, // [31:31] is the sub-list for extension extendee
|
38, // [38:44] is the sub-list for method output_type
|
||||||
0, // [0:31] is the sub-list for field type_name
|
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() }
|
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{} {
|
file_management_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*ProviderConfig); i {
|
switch v := v.(*PKCEAuthorizationFlowRequest); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -2719,7 +2850,7 @@ func file_management_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_management_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
|
file_management_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*Route); i {
|
switch v := v.(*PKCEAuthorizationFlow); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -2731,7 +2862,7 @@ func file_management_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_management_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
|
file_management_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*DNSConfig); i {
|
switch v := v.(*ProviderConfig); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -2743,7 +2874,7 @@ func file_management_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_management_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
|
file_management_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*CustomZone); i {
|
switch v := v.(*Route); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -2755,7 +2886,7 @@ func file_management_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_management_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
|
file_management_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*SimpleRecord); i {
|
switch v := v.(*DNSConfig); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -2767,7 +2898,7 @@ func file_management_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_management_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
|
file_management_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*NameServerGroup); i {
|
switch v := v.(*CustomZone); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -2779,7 +2910,7 @@ func file_management_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_management_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
|
file_management_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*NameServer); i {
|
switch v := v.(*SimpleRecord); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -2791,6 +2922,30 @@ func file_management_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_management_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
|
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 {
|
switch v := v.(*FirewallRule); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -2809,7 +2964,7 @@ func file_management_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_management_proto_rawDesc,
|
RawDescriptor: file_management_proto_rawDesc,
|
||||||
NumEnums: 5,
|
NumEnums: 5,
|
||||||
NumMessages: 26,
|
NumMessages: 28,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ service ManagementService {
|
|||||||
// EncryptedMessage of the request has a body of DeviceAuthorizationFlowRequest.
|
// EncryptedMessage of the request has a body of DeviceAuthorizationFlowRequest.
|
||||||
// EncryptedMessage of the response has a body of DeviceAuthorizationFlow.
|
// EncryptedMessage of the response has a body of DeviceAuthorizationFlow.
|
||||||
rpc GetDeviceAuthorizationFlow(EncryptedMessage) returns (EncryptedMessage) {}
|
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 {
|
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 {
|
message ProviderConfig {
|
||||||
// An IDP application client id
|
// An IDP application client id
|
||||||
string ClientID = 1;
|
string ClientID = 1;
|
||||||
@@ -256,6 +273,10 @@ message ProviderConfig {
|
|||||||
string Scope = 7;
|
string Scope = 7;
|
||||||
// UseIDToken indicates if the id token should be used for authentication
|
// UseIDToken indicates if the id token should be used for authentication
|
||||||
bool UseIDToken = 8;
|
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
|
// Route represents a route.Route object
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ type ManagementServiceClient interface {
|
|||||||
// EncryptedMessage of the request has a body of DeviceAuthorizationFlowRequest.
|
// EncryptedMessage of the request has a body of DeviceAuthorizationFlowRequest.
|
||||||
// EncryptedMessage of the response has a body of DeviceAuthorizationFlow.
|
// EncryptedMessage of the response has a body of DeviceAuthorizationFlow.
|
||||||
GetDeviceAuthorizationFlow(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error)
|
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 {
|
type managementServiceClient struct {
|
||||||
@@ -115,6 +121,15 @@ func (c *managementServiceClient) GetDeviceAuthorizationFlow(ctx context.Context
|
|||||||
return out, nil
|
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.
|
// ManagementServiceServer is the server API for ManagementService service.
|
||||||
// All implementations must embed UnimplementedManagementServiceServer
|
// All implementations must embed UnimplementedManagementServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@@ -138,6 +153,12 @@ type ManagementServiceServer interface {
|
|||||||
// EncryptedMessage of the request has a body of DeviceAuthorizationFlowRequest.
|
// EncryptedMessage of the request has a body of DeviceAuthorizationFlowRequest.
|
||||||
// EncryptedMessage of the response has a body of DeviceAuthorizationFlow.
|
// EncryptedMessage of the response has a body of DeviceAuthorizationFlow.
|
||||||
GetDeviceAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error)
|
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()
|
mustEmbedUnimplementedManagementServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +181,9 @@ func (UnimplementedManagementServiceServer) IsHealthy(context.Context, *Empty) (
|
|||||||
func (UnimplementedManagementServiceServer) GetDeviceAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
|
func (UnimplementedManagementServiceServer) GetDeviceAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetDeviceAuthorizationFlow not implemented")
|
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() {}
|
func (UnimplementedManagementServiceServer) mustEmbedUnimplementedManagementServiceServer() {}
|
||||||
|
|
||||||
// UnsafeManagementServiceServer may be embedded to opt out of forward compatibility for this service.
|
// 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)
|
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.
|
// ManagementService_ServiceDesc is the grpc.ServiceDesc for ManagementService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -289,6 +331,10 @@ var ManagementService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetDeviceAuthorizationFlow",
|
MethodName: "GetDeviceAuthorizationFlow",
|
||||||
Handler: _ManagementService_GetDeviceAuthorizationFlow_Handler,
|
Handler: _ManagementService_GetDeviceAuthorizationFlow_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetPKCEAuthorizationFlow",
|
||||||
|
Handler: _ManagementService_GetPKCEAuthorizationFlow_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ type Config struct {
|
|||||||
IdpManagerConfig *idp.Config
|
IdpManagerConfig *idp.Config
|
||||||
|
|
||||||
DeviceAuthorizationFlow *DeviceAuthorizationFlow
|
DeviceAuthorizationFlow *DeviceAuthorizationFlow
|
||||||
|
|
||||||
|
PKCEAuthorizationFlow *PKCEAuthorizationFlow
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthAudiences returns the audience from the http config and device authorization flow config
|
// GetAuthAudiences returns the audience from the http config and device authorization flow config
|
||||||
@@ -101,7 +103,14 @@ type DeviceAuthorizationFlow struct {
|
|||||||
ProviderConfig ProviderConfig
|
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 {
|
type ProviderConfig struct {
|
||||||
// ClientID An IDP application client id
|
// ClientID An IDP application client id
|
||||||
ClientID string
|
ClientID string
|
||||||
@@ -116,10 +125,14 @@ type ProviderConfig struct {
|
|||||||
TokenEndpoint string
|
TokenEndpoint string
|
||||||
// DeviceAuthEndpoint is the endpoint of an IDP manager where clients can obtain device authorization code
|
// DeviceAuthEndpoint is the endpoint of an IDP manager where clients can obtain device authorization code
|
||||||
DeviceAuthEndpoint string
|
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
|
// Scopes provides the scopes to be included in the token request
|
||||||
Scope string
|
Scope string
|
||||||
// UseIDToken indicates if the id token should be used for authentication
|
// UseIDToken indicates if the id token should be used for authentication
|
||||||
UseIDToken bool
|
UseIDToken bool
|
||||||
|
// RedirectURL handles authorization code from IDP manager
|
||||||
|
RedirectURLs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateURL validates input http url
|
// validateURL validates input http url
|
||||||
|
|||||||
@@ -543,3 +543,49 @@ func (s *GRPCServer) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.
|
|||||||
Body: encryptedResp,
|
Body: encryptedResp,
|
||||||
}, nil
|
}, 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type ManagementServiceServerMock struct {
|
|||||||
GetServerKeyFunc func(context.Context, *proto.Empty) (*proto.ServerKeyResponse, error)
|
GetServerKeyFunc func(context.Context, *proto.Empty) (*proto.ServerKeyResponse, error)
|
||||||
IsHealthyFunc func(context.Context, *proto.Empty) (*proto.Empty, error)
|
IsHealthyFunc func(context.Context, *proto.Empty) (*proto.Empty, error)
|
||||||
GetDeviceAuthorizationFlowFunc func(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, 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) {
|
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")
|
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")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user