Skip to content

Commit 58c0b9c

Browse files
committed
allow application to create ziti context without credentials and then query for external providers
simplify using external JWT for authentication
1 parent 0351630 commit 58c0b9c

File tree

4 files changed

+171
-5
lines changed

4 files changed

+171
-5
lines changed

edge-apis/clients.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,17 @@ package edge_apis
1818

1919
import (
2020
"crypto/x509"
21+
"net/http"
22+
"net/url"
23+
"strings"
24+
"sync/atomic"
25+
2126
"github.com/go-openapi/runtime"
2227
openapiclient "github.com/go-openapi/runtime/client"
2328
"github.com/go-openapi/strfmt"
2429
"github.com/michaelquigley/pfxlog"
2530
"github.com/openziti/edge-api/rest_client_api_client"
2631
"github.com/openziti/edge-api/rest_management_api_client"
27-
"net/http"
28-
"net/url"
29-
"strings"
30-
"sync/atomic"
3132
)
3233

3334
// ApiType is an interface constraint for generics. The underlying go-swagger types only have fields, which are
@@ -173,7 +174,7 @@ func (self *BaseClient[A]) ProcessControllers(authEnabledApi AuthEnabledApi) {
173174
list, err := authEnabledApi.ListControllers()
174175

175176
if err != nil {
176-
pfxlog.Logger().WithError(err).Error("error listing controllers, continuing with 1 default configured controller")
177+
pfxlog.Logger().WithError(err).Debug("error listing controllers, continuing with 1 default configured controller")
177178
return
178179
}
179180

example/device-auth/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
OIDC Device Code authentication example
2+
---
3+
4+
This sample shows OpenZiti OIDC authentication with device code flow.
5+
Prerequisites:
6+
- your OpenZiti network is configured with an external OIDC provider
7+
- your OIDC provider is configured to allow device code flow

example/device-auth/main.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"flag"
6+
"fmt"
7+
"log"
8+
"net/http"
9+
"net/url"
10+
"strings"
11+
"time"
12+
13+
"github.com/dgrijalva/jwt-go"
14+
"github.com/openziti/edge-api/rest_model"
15+
"github.com/openziti/edge-api/rest_util"
16+
nfx509 "github.com/openziti/foundation/v2/x509"
17+
"github.com/openziti/sdk-golang/ziti"
18+
"gopkg.in/square/go-jose.v2/json"
19+
)
20+
21+
func die[T interface{}](res T, err error) T {
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
return res
26+
}
27+
28+
func main() {
29+
cfg := flag.String("config", "", "path to config file")
30+
openzitiURL := flag.String("ziti", "https://localhost:1280", "URL of the OpenZiti service")
31+
flag.Parse()
32+
33+
var config *ziti.Config
34+
if cfg == nil || *cfg == "" {
35+
config = &ziti.Config{
36+
ZtAPI: *openzitiURL,
37+
}
38+
// warning: this call is insecure and should not be used in production
39+
ca := die(rest_util.GetControllerWellKnownCas(*openzitiURL))
40+
var buf bytes.Buffer
41+
_ = nfx509.MarshalToPem(ca, &buf)
42+
config.ID.CA = buf.String()
43+
} else {
44+
if openzitiURL == nil || *openzitiURL == "" {
45+
log.Fatal("OpenZiti URL must be specified")
46+
}
47+
config = die(ziti.NewConfigFromFile(*cfg))
48+
}
49+
ztx := die(ziti.NewContext(config))
50+
51+
err := ztx.Authenticate()
52+
var provider *rest_model.ClientExternalJWTSignerDetail
53+
if err != nil {
54+
fmt.Println("Try authenticating with external provider")
55+
idps := die(ztx.GetExternalSigners())
56+
for idx, idp := range idps {
57+
fmt.Printf("%d: %s\n", idx, *idp.Name)
58+
}
59+
60+
fmt.Printf("Select provider allowing device code flow.\nEnter number[0-%d] to authenticate: ", len(idps)-1)
61+
var id int
62+
_ = die(fmt.Scanf("%d", &id))
63+
64+
provider = idps[id]
65+
}
66+
if provider == nil {
67+
log.Fatal("No provider found")
68+
}
69+
fmt.Printf("Using %s\n", *provider.Name)
70+
71+
resp := die(http.Get(*provider.ExternalAuthURL + "/.well-known/openid-configuration"))
72+
var oidcConfig map[string]interface{}
73+
_ = json.NewDecoder(resp.Body).Decode(&oidcConfig)
74+
75+
deviceAuth := oidcConfig["device_authorization_endpoint"].(string)
76+
scopes := append(provider.Scopes, "openid")
77+
ss := strings.Join(scopes, " ")
78+
resp = die(http.PostForm(deviceAuth, url.Values{
79+
"client_id": {*provider.ClientID},
80+
"scope": {ss},
81+
"audience": {*provider.Audience},
82+
}))
83+
84+
var deviceCode map[string]interface{}
85+
_ = json.NewDecoder(resp.Body).Decode(&deviceCode)
86+
if completeUrl, ok := deviceCode["verification_uri_complete"]; ok {
87+
fmt.Printf("Open %s in your browser\n", completeUrl.(string))
88+
} else if verifyUrl, ok := deviceCode["verification_uri"]; ok {
89+
fmt.Printf("Open %s in your browser, and use code %s\n",
90+
verifyUrl.(string), deviceCode["user_code"].(string))
91+
} else {
92+
log.Fatal("Unable to determine verification URL")
93+
}
94+
95+
interval := time.Duration(int(deviceCode["interval"].(float64))) * time.Second
96+
97+
var token map[string]interface{}
98+
for {
99+
clear(token)
100+
time.Sleep(interval)
101+
102+
tokenUrl := oidcConfig["token_endpoint"].(string)
103+
resp = die(http.PostForm(tokenUrl, url.Values{
104+
"client_id": {*provider.ClientID},
105+
"device_code": {deviceCode["device_code"].(string)},
106+
"grant_type": {"urn:ietf:params:oauth:grant-type:device_code"},
107+
}))
108+
109+
json.NewDecoder(resp.Body).Decode(&token)
110+
errmsg, hasErr := token["error"]
111+
if !hasErr {
112+
break
113+
}
114+
errormsg := errmsg.(string)
115+
if errormsg == "authorization_pending" {
116+
fmt.Println("Waiting for user to authorize...")
117+
continue
118+
}
119+
log.Fatal(errormsg)
120+
}
121+
122+
accessToken := token["access_token"].(string)
123+
tok, _ := jwt.Parse(accessToken, nil)
124+
if claims, ok := tok.Claims.(jwt.MapClaims); ok {
125+
for k, v := range claims {
126+
fmt.Printf("\t%s: %v\n", k, v)
127+
}
128+
}
129+
ztx.LoginWithJWT(accessToken)
130+
131+
err = ztx.Authenticate()
132+
if err != nil {
133+
log.Fatal(err)
134+
}
135+
fmt.Println("Authenticated")
136+
137+
services, _ := ztx.GetServices()
138+
fmt.Println("Available Services:")
139+
for _, svc := range services {
140+
fmt.Printf("\t%s\n", *svc.Name)
141+
}
142+
}

ziti/ziti.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ type Context interface {
9595
// SetCredentials sets the credentials used to authenticate against the Edge Client API.
9696
SetCredentials(authenticator apis.Credentials)
9797

98+
LoginWithJWT(jst string)
99+
98100
// GetCredentials returns the currently set credentials used to authenticate against the Edge Client API.
99101
GetCredentials() apis.Credentials
100102

@@ -486,6 +488,20 @@ func (context *ContextImpl) SetCredentials(credentials apis.Credentials) {
486488
context.CtrlClt.Credentials = credentials
487489
}
488490

491+
func (context *ContextImpl) LoginWithJWT(jwt string) {
492+
cred := context.CtrlClt.Credentials
493+
jwtCred := &apis.JwtCredentials{
494+
BaseCredentials: apis.BaseCredentials{
495+
ConfigTypes: cred.Payload().ConfigTypes,
496+
EnvInfo: cred.Payload().EnvInfo,
497+
SdkInfo: cred.Payload().SdkInfo,
498+
CaPool: context.CtrlClt.CaPool,
499+
},
500+
JWT: jwt,
501+
}
502+
context.SetCredentials(jwtCred)
503+
}
504+
489505
func (context *ContextImpl) GetCredentials() apis.Credentials {
490506
return context.CtrlClt.Credentials
491507
}

0 commit comments

Comments
 (0)