Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redo OIDC configuration #2020

Merged
merged 1 commit into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,25 @@

## Next

### BREAKING

- Remove `dns.use_username_in_magic_dns` configuration option [#2020](https://github.com/juanfont/headscale/pull/2020)
- Having usernames in magic DNS is no longer possible.
- Redo OpenID Connect configuration [#2020](https://github.com/juanfont/headscale/pull/2020)
- `strip_email_domain` has been removed, domain is _always_ part of the username for OIDC.
- Users are now identified by `sub` claim in the ID token instead of username, allowing the username, name and email to be updated.
- User has been extended to store username, display name, profile picture url and email.
- These fields are forwarded to the client, and shows up nicely in the user switcher.
- These fields can be made available via the API/CLI for non-OIDC users in the future.
- Remove versions older than 1.56 [#2149](https://github.com/juanfont/headscale/pull/2149)
- Clean up old code required by old versions

### Changes

- Improved compatibilty of built-in DERP server with clients connecting over WebSocket.
- Allow nodes to use SSH agent forwarding [#2145](https://github.com/juanfont/headscale/pull/2145)


## 0.23.0 (2024-09-18)

This release was intended to be mainly a code reorganisation and refactoring, significantly improving the maintainability of the codebase. This should allow us to improve further and make it easier for the maintainers to keep on top of the project.
Expand Down
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

# When updating go.mod or go.sum, a new sha will need to be calculated,
# update this if you have a mismatch after doing a change to thos files.
vendorHash = "sha256-/CPUkLLCwNKK3z3UZyF+AY0ArMnLaDmH0HV3/RYHo4c=";
vendorHash = "sha256-SDJSFji6498WI9bJLmY62VGt21TtD2GxrxRAWyYyr0c=";

subPackages = ["cmd/headscale"];

Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ require (
github.com/coder/websocket v1.8.12
github.com/coreos/go-oidc/v3 v3.11.0
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/deckarep/golang-set/v2 v2.6.0
github.com/glebarez/sqlite v1.11.0
github.com/go-gormigrate/gormigrate/v2 v2.1.2
github.com/gofrs/uuid/v5 v5.3.0
Expand All @@ -19,7 +18,6 @@ require (
github.com/klauspost/compress v1.17.9
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
github.com/ory/dockertest/v3 v3.11.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/philip-bui/grpc-zerolog v1.0.1
github.com/pkg/profile v1.7.0
github.com/prometheus/client_golang v1.20.2
Expand Down Expand Up @@ -49,6 +47,7 @@ require (
gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.11
tailscale.com v1.75.0-pre.0.20240926101731-7d1160ddaab7
zgo.at/zcache/v2 v2.1.0
)

require (
Expand Down
12 changes: 2 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dblohm7/wingoes v0.0.0-20240123200102-b75a8a7d7eb0 h1:vrC07UZcgPzu/OjWsmQKMGg3LoPSz9jh/pQXIrHjUj4=
github.com/dblohm7/wingoes v0.0.0-20240123200102-b75a8a7d7eb0/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
Expand Down Expand Up @@ -364,8 +362,6 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA=
github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
Expand Down Expand Up @@ -729,11 +725,7 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
tailscale.com v1.75.0-pre.0.20240925091311-031f291c98fe h1:3+E/vlEsZa2FpWBz2Ly6/L4zh4utVO8z54Ms75HitrQ=
tailscale.com v1.75.0-pre.0.20240925091311-031f291c98fe/go.mod h1:G4R9objdXe2zAcLaLkDOcHfqN9XnspBifyBHGNwTzKg=
tailscale.com v1.75.0-pre.0.20240925102642-c17c476c0d59 h1:GSuB+bmPiVfBLRqVyLOFSU+9V00lXBz9HakAewevYZA=
tailscale.com v1.75.0-pre.0.20240925102642-c17c476c0d59/go.mod h1:G4R9objdXe2zAcLaLkDOcHfqN9XnspBifyBHGNwTzKg=
tailscale.com v1.75.0-pre.0.20240926030905-c90c9938c8a2 h1:ivZ1GEXMzCNI1VRp2TjUWmLuOtno7TqW26lZf7MlF4k=
tailscale.com v1.75.0-pre.0.20240926030905-c90c9938c8a2/go.mod h1:xKxYf3B3PuezFlRaMT+VhuVu8XTFUTLy+VCzLPMJVmg=
tailscale.com v1.75.0-pre.0.20240926101731-7d1160ddaab7 h1:nfRWV6ECxwNvvXKtbqSVstjlEi1BWktzv3FuxWpyyx0=
tailscale.com v1.75.0-pre.0.20240926101731-7d1160ddaab7/go.mod h1:xKxYf3B3PuezFlRaMT+VhuVu8XTFUTLy+VCzLPMJVmg=
zgo.at/zcache/v2 v2.1.0 h1:USo+ubK+R4vtjw4viGzTe/zjXyPw6R7SK/RL3epBBxs=
zgo.at/zcache/v2 v2.1.0/go.mod h1:gyCeoLVo01QjDZynjime8xUGHHMbsLiPyUTBpDGd4Gk=
38 changes: 26 additions & 12 deletions hscontrol/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"syscall"
"time"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/davecgh/go-spew/spew"
"github.com/gorilla/mux"
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
Expand All @@ -33,15 +32,13 @@ import (
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/patrickmn/go-cache"
zerolog "github.com/philip-bui/grpc-zerolog"
"github.com/pkg/profile"
"github.com/prometheus/client_golang/prometheus/promhttp"
zl "github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/oauth2"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
Expand All @@ -57,6 +54,7 @@ import (
"tailscale.com/types/dnstype"
"tailscale.com/types/key"
"tailscale.com/util/dnsname"
zcache "zgo.at/zcache/v2"
)

var (
Expand Down Expand Up @@ -95,10 +93,9 @@ type Headscale struct {
mapper *mapper.Mapper
nodeNotifier *notifier.Notifier

oidcProvider *oidc.Provider
oauth2Config *oauth2.Config
registrationCache *zcache.Cache[string, types.Node]

registrationCache *cache.Cache
authProvider AuthProvider

pollNetMapStreamWG sync.WaitGroup
}
Expand All @@ -123,7 +120,7 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) {
return nil, fmt.Errorf("failed to read or create Noise protocol private key: %w", err)
}

registrationCache := cache.New(
registrationCache := zcache.New[string, types.Node](
registerCacheExpiration,
registerCacheCleanup,
)
Expand All @@ -138,7 +135,9 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) {

app.db, err = db.NewHeadscaleDatabase(
cfg.Database,
cfg.BaseDomain)
cfg.BaseDomain,
registrationCache,
)
if err != nil {
return nil, err
}
Expand All @@ -154,16 +153,30 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) {
}
})

var authProvider AuthProvider
authProvider = NewAuthProviderWeb(cfg.ServerURL)
if cfg.OIDC.Issuer != "" {
err = app.initOIDC()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
oidcProvider, err := NewAuthProviderOIDC(
ctx,
cfg.ServerURL,
&cfg.OIDC,
app.db,
app.nodeNotifier,
app.ipAlloc,
)
if err != nil {
if cfg.OIDC.OnlyStartIfOIDCIsAvailable {
return nil, err
} else {
log.Warn().Err(err).Msg("failed to set up OIDC provider, falling back to CLI based authentication")
}
} else {
authProvider = oidcProvider
}
}
app.authProvider = authProvider

if app.cfg.DNSConfig != nil && app.cfg.DNSConfig.Proxied { // if MagicDNS
// TODO(kradalby): revisit why this takes a list.
Expand Down Expand Up @@ -429,10 +442,11 @@ func (h *Headscale) createRouter(grpcMux *grpcRuntime.ServeMux) *mux.Router {

router.HandleFunc("/health", h.HealthHandler).Methods(http.MethodGet)
router.HandleFunc("/key", h.KeyHandler).Methods(http.MethodGet)
router.HandleFunc("/register/{mkey}", h.RegisterWebAPI).Methods(http.MethodGet)
router.HandleFunc("/register/{mkey}", h.authProvider.RegisterHandler).Methods(http.MethodGet)

router.HandleFunc("/oidc/register/{mkey}", h.RegisterOIDC).Methods(http.MethodGet)
router.HandleFunc("/oidc/callback", h.OIDCCallback).Methods(http.MethodGet)
if provider, ok := h.authProvider.(*AuthProviderOIDC); ok {
router.HandleFunc("/oidc/callback", provider.OIDCCallbackHandler).Methods(http.MethodGet)
}
router.HandleFunc("/apple", h.AppleConfigMessage).Methods(http.MethodGet)
router.HandleFunc("/apple/{platform}", h.ApplePlatformConfig).
Methods(http.MethodGet)
Expand Down
41 changes: 10 additions & 31 deletions hscontrol/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"net/http"
"strings"
"time"

"github.com/juanfont/headscale/hscontrol/db"
Expand All @@ -19,6 +18,11 @@ import (
"tailscale.com/types/ptr"
)

type AuthProvider interface {
RegisterHandler(http.ResponseWriter, *http.Request)
AuthURL(key.MachinePublic) string
}

func logAuthFunc(
registerRequest tailcfg.RegisterRequest,
machineKey key.MachinePublic,
Expand Down Expand Up @@ -125,7 +129,6 @@ func (h *Headscale) handleRegister(
h.registrationCache.Set(
machineKey.String(),
newNode,
registerCacheExpiration,
)

h.handleNewNode(writer, regReq, machineKey)
Expand Down Expand Up @@ -164,15 +167,15 @@ func (h *Headscale) handleRegister(
// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L648
if !regReq.Expiry.IsZero() &&
regReq.Expiry.UTC().Before(now) {
h.handleNodeLogOut(writer, *node, machineKey)
h.handleNodeLogOut(writer, *node)

return
}

// If node is not expired, and it is register, we have a already accepted this node,
// let it proceed with a valid registration
if !node.IsExpired() {
h.handleNodeWithValidRegistration(writer, *node, machineKey)
h.handleNodeWithValidRegistration(writer, *node)

return
}
Expand All @@ -185,7 +188,6 @@ func (h *Headscale) handleRegister(
writer,
regReq,
*node,
machineKey,
)

return
Expand All @@ -198,7 +200,6 @@ func (h *Headscale) handleRegister(
writer,
regReq,
*node,
machineKey,
)

return
Expand Down Expand Up @@ -226,7 +227,6 @@ func (h *Headscale) handleRegister(
h.registrationCache.Set(
machineKey.String(),
*node,
registerCacheExpiration,
)

return
Expand Down Expand Up @@ -386,7 +386,7 @@ func (h *Headscale) handleAuthKey(
}
}

h.db.Write(func(tx *gorm.DB) error {
err = h.db.Write(func(tx *gorm.DB) error {
return db.UsePreAuthKey(tx, pak)
})
if err != nil {
Expand Down Expand Up @@ -447,17 +447,7 @@ func (h *Headscale) handleNewNode(
// The node registration is new, redirect the client to the registration URL
logTrace("The node seems to be new, sending auth url")

if h.oauth2Config != nil {
resp.AuthURL = fmt.Sprintf(
"%s/oidc/register/%s",
strings.TrimSuffix(h.cfg.ServerURL, "/"),
machineKey.String(),
)
} else {
resp.AuthURL = fmt.Sprintf("%s/register/%s",
strings.TrimSuffix(h.cfg.ServerURL, "/"),
machineKey.String())
}
resp.AuthURL = h.authProvider.AuthURL(machineKey)

respBody, err := json.Marshal(resp)
if err != nil {
Expand All @@ -480,7 +470,6 @@ func (h *Headscale) handleNewNode(
func (h *Headscale) handleNodeLogOut(
writer http.ResponseWriter,
node types.Node,
machineKey key.MachinePublic,
) {
resp := tailcfg.RegisterResponse{}

Expand Down Expand Up @@ -563,7 +552,6 @@ func (h *Headscale) handleNodeLogOut(
func (h *Headscale) handleNodeWithValidRegistration(
writer http.ResponseWriter,
node types.Node,
machineKey key.MachinePublic,
) {
resp := tailcfg.RegisterResponse{}

Expand Down Expand Up @@ -609,7 +597,6 @@ func (h *Headscale) handleNodeKeyRefresh(
writer http.ResponseWriter,
registerRequest tailcfg.RegisterRequest,
node types.Node,
machineKey key.MachinePublic,
) {
resp := tailcfg.RegisterResponse{}

Expand Down Expand Up @@ -685,15 +672,7 @@ func (h *Headscale) handleNodeExpiredOrLoggedOut(
Str("node_key_old", regReq.OldNodeKey.ShortString()).
Msg("Node registration has expired or logged out. Sending a auth url to register")

if h.oauth2Config != nil {
resp.AuthURL = fmt.Sprintf("%s/oidc/register/%s",
strings.TrimSuffix(h.cfg.ServerURL, "/"),
machineKey.String())
} else {
resp.AuthURL = fmt.Sprintf("%s/register/%s",
strings.TrimSuffix(h.cfg.ServerURL, "/"),
machineKey.String())
}
resp.AuthURL = h.authProvider.AuthURL(machineKey)

respBody, err := json.Marshal(resp)
if err != nil {
Expand Down
Loading
Loading