Skip to content

Commit

Permalink
Add a tokenserver that hands out (invitation-) tokens based on OIDC
Browse files Browse the repository at this point in the history
closes #115
  • Loading branch information
Jille committed Aug 18, 2024
1 parent 4be4bac commit 72bf2aa
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cmd/tokenserver/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tokenserver
settings.json
187 changes: 187 additions & 0 deletions cmd/tokenserver/tokenserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package main

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"net/url"
"os"
"strings"

"github.com/Jille/convreq"
"github.com/Jille/convreq/respond"
"github.com/Jille/rufs/security"
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
)

var (
ca *security.CAKeyPair

oidcProvider *oidc.Provider
oauthConfig oauth2.Config
)

type Config struct {
Certdir string `json:"certdir"`
OIDCProvider string `json:"oidc_provider"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
ReturnURL string `json:"return_url"`
Port int `json:"port"`
}

func main() {
if len(os.Args) != 2 {
log.Fatalf("Usage: %s <configfile>", os.Args[0])
}
c, err := os.ReadFile(os.Args[1])
if err != nil {
log.Fatalf("Failed to read config file %q: %v", os.Args[1], err)
}
var cfg Config
if err := json.Unmarshal(c, &cfg); err != nil {
log.Fatalf("Failed to read config file %q: %v", os.Args[1], err)
}

if cfg.Certdir == "" {
log.Fatalf("Configuration setting \"certdir\" is empty")
}
if cfg.OIDCProvider == "" {
log.Fatalf("Configuration setting \"oidc_provider\" is empty")
}
if cfg.ClientID == "" {
log.Fatalf("Configuration setting \"client_id\" is empty")
}
if cfg.ClientSecret == "" {
log.Fatalf("Configuration setting \"client_secret\" is empty")
}
if cfg.ReturnURL == "" {
log.Fatalf("Configuration setting \"return_url\" is empty")
}
if cfg.Port == 0 {
log.Fatalf("Configuration setting \"port\" is not set")
}

ca, err = security.LoadCAKeyPair(cfg.Certdir)
if err != nil {
log.Fatalf("Failed to load CA key pair: %v", err)
}

oidcProvider, err = oidc.NewProvider(context.Background(), cfg.OIDCProvider)
if err != nil {
log.Fatalf("Failed to discover openid connect provider: %v", err)
}
oauthConfig = oauth2.Config{
ClientID: cfg.ClientID,
ClientSecret: cfg.ClientSecret,
Endpoint: oidcProvider.Endpoint(),
RedirectURL: cfg.ReturnURL,
Scopes: []string{"openid"},
}

http.Handle("/oauth2_return", convreq.Wrap(oauth2Return))
http.Handle("/request_token", convreq.Wrap(requestToken))
http.Handle("/", convreq.Wrap(mainPage))
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", cfg.Port), nil))
}

type requestTokenGet struct {
Hostname string `schema:"hostname"`
ReturnURL string `schema:"return_url"`
}

func requestToken(get requestTokenGet) convreq.HttpResponse {
s := base64.URLEncoding.EncodeToString([]byte(get.Hostname)) + ":" + base64.URLEncoding.EncodeToString([]byte(get.ReturnURL))
u := oauthConfig.AuthCodeURL(s)
return respond.Redirect(302, u)
}

type oauth2ReturnGet struct {
Code string `schema:"code"`
State string `schema:"state"`
}

func oauth2Return(ctx context.Context, get oauth2ReturnGet) convreq.HttpResponse {
t, err := oauthConfig.Exchange(ctx, get.Code)
if err != nil {
return respond.BadRequest("Oauth failed: " + err.Error())
}
userInfo, err := oidcProvider.UserInfo(ctx, oauth2.StaticTokenSource(t))
if err != nil {
return respond.Error(err)
}
var ui UserInfo
if err := userInfo.Claims(&ui); err != nil {
return respond.Error(err)
}

sp := strings.Split(get.State, ":")
hostname, err := base64.URLEncoding.DecodeString(sp[0])
if err != nil {
return respond.BadRequest("bad state")
}
returnURL, err := base64.URLEncoding.DecodeString(sp[1])
if err != nil {
return respond.BadRequest("bad state")
}

username := strings.Split(ui.PreferredUsername, "@")[0]
username = strings.ReplaceAll(username, "-", "")
username += "-" + string(hostname)

token := ca.CreateToken(username)

u, err := url.Parse(string(returnURL))
if err != nil {
return respond.BadRequest("Invalid return_url: " + err.Error())
}
q := u.Query()
q.Set("username", username)
q.Set("token", token)
q.Set("circle", ca.Name())
q.Set("fingerprint", ca.Fingerprint())

if len(returnURL) == 0 {
var ret string
for k, vs := range q {
ret += k + ":" + vs[0] + "\n"
}
return respond.String(ret)
}

u.RawQuery = q.Encode()
return respond.Redirect(302, u.String())
}

type UserInfo struct {
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
}

type mainPageGet struct {
Hostname string `schema:"hostname"`
ReturnURL string `schema:"return_url"`
}

var mainPageTpl = template.Must(template.New("").Parse(`
<html>
<head>
</head>
<body>
<form method="GET" action="/request_token">
Hostname: <input name="hostname" value="{{.Hostname}}" />
<input type="hidden" name="return_url" value="{{.ReturnURL}}" />
<input type="submit" value="Continue" />
</form>
</body>
</html>
`))

func mainPage(get mainPageGet) convreq.HttpResponse {
return respond.RenderTemplate(mainPageTpl, get)
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/Jille/rpcz v0.3.0
github.com/billziss-gh/cgofuse v1.5.0
github.com/cenkalti/backoff/v4 v4.3.0
github.com/coreos/go-oidc/v3 v3.11.0
github.com/getlantern/systray v1.2.2
github.com/go-git/go-billy/v5 v5.5.0
github.com/golang/protobuf v1.5.4
Expand All @@ -27,6 +28,7 @@ require (
github.com/prometheus/client_golang v1.12.2
github.com/stoewer/go-strcase v1.3.0
github.com/yookoala/realpath v1.0.0
golang.org/x/oauth2 v0.21.0
golang.org/x/sync v0.8.0
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.2
Expand All @@ -44,6 +46,7 @@ require (
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc // indirect
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 // indirect
github.com/getlantern/ops v0.0.0-20231025133620-f368ab734534 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-stack/stack v1.8.1 // indirect
Expand All @@ -60,6 +63,7 @@ require (
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
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/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.3.1 h1:1V7cHiaW+C+39wEfpH6XlLBQo3j/PciWFrgfCLS8XrE=
github.com/cyphar/filepath-securejoin v0.3.1/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc=
Expand Down Expand Up @@ -125,6 +127,8 @@ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgF
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
Expand Down Expand Up @@ -353,6 +357,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand Down Expand Up @@ -425,6 +431,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down

0 comments on commit 72bf2aa

Please sign in to comment.