Skip to content

Commit

Permalink
Merge branch 'main' into kradalby/dockerfile-rename
Browse files Browse the repository at this point in the history
  • Loading branch information
juanfont authored Nov 23, 2024
2 parents 137f0e3 + f6276ab commit ccc2dac
Show file tree
Hide file tree
Showing 75 changed files with 2,815 additions and 896 deletions.
16 changes: 8 additions & 8 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
* @juanfont @kradalby

*.md @ohdearaugustin
*.yml @ohdearaugustin
*.yaml @ohdearaugustin
Dockerfile* @ohdearaugustin
.goreleaser.yaml @ohdearaugustin
/docs/ @ohdearaugustin
/.github/workflows/ @ohdearaugustin
/.github/renovate.json @ohdearaugustin
*.md @ohdearaugustin @nblock
*.yml @ohdearaugustin @nblock
*.yaml @ohdearaugustin @nblock
Dockerfile* @ohdearaugustin @nblock
.goreleaser.yaml @ohdearaugustin @nblock
/docs/ @ohdearaugustin @nblock
/.github/workflows/ @ohdearaugustin @nblock
/.github/renovate.json @ohdearaugustin @nblock
2 changes: 2 additions & 0 deletions .github/workflows/test-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- TestPolicyUpdateWhileRunningWithCLIInDatabase
- TestOIDCAuthenticationPingAll
- TestOIDCExpireNodesBasedOnTokenExpiry
- TestOIDC024UserCreation
- TestAuthWebFlowAuthenticationPingAll
- TestAuthWebFlowLogoutAndRelogin
- TestUserCommand
Expand All @@ -38,6 +39,7 @@ jobs:
- TestNodeMoveCommand
- TestPolicyCommand
- TestPolicyBrokenConfigCommand
- TestDERPVerifyEndpoint
- TestResolveMagicDNS
- TestValidateResolvConf
- TestDERPServerScenario
Expand Down
6 changes: 6 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ linters:
- nolintlint
- musttag # causes issues with imported libs
- depguard
- exportloopref

# We should strive to enable these:
- wrapcheck
Expand Down Expand Up @@ -56,9 +57,14 @@ linters-settings:
- ok
- c
- tt
- tx
- rx

gocritic:
disabled-checks:
- appendAssign
# TODO(kradalby): Remove this
- ifElseChain

nlreturn:
block-size: 4
4 changes: 2 additions & 2 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ kos:
- "{{ if not .Prerelease }}v{{ .Major }}.{{ .Minor }}.{{ .Patch }}-debug{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}.{{ .Minor }}-debug{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}-debug{{ end }}"
- "{{ if not .Prerelease }}stable{{ else }}unstable-debug{{ end }}"
- "{{ if not .Prerelease }}stable-debug{{ else }}unstable-debug{{ end }}"
- "{{ .Tag }}-debug"
- '{{ trimprefix .Tag "v" }}-debug'
- "sha-{{ .ShortCommit }}-debug"
Expand All @@ -177,7 +177,7 @@ kos:
- "{{ if not .Prerelease }}v{{ .Major }}.{{ .Minor }}.{{ .Patch }}-debug{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}.{{ .Minor }}-debug{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}-debug{{ end }}"
- "{{ if not .Prerelease }}stable{{ else }}unstable-debug{{ end }}"
- "{{ if not .Prerelease }}stable-debug{{ else }}unstable-debug{{ end }}"
- "{{ .Tag }}-debug"
- '{{ trimprefix .Tag "v" }}-debug'
- "sha-{{ .ShortCommit }}-debug"
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.github/workflows/test-integration-v2*
docs/about/features.md
docs/ref/remote-cli.md
80 changes: 74 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,82 @@

## Next

### Security fix: OIDC changes in Headscale 0.24.0

_Headscale v0.23.0 and earlier_ identified OIDC users by the "username" part of their email address (when `strip_email_domain: true`, the default) or whole email address (when `strip_email_domain: false`).

Depending on how Headscale and your Identity Provider (IdP) were configured, only using the `email` claim could allow a malicious user with an IdP account to take over another Headscale user's account, even when `strip_email_domain: false`.

This would also cause a user to lose access to their Headscale account if they changed their email address.

_Headscale v0.24.0_ now identifies OIDC users by the `iss` and `sub` claims. [These are guaranteed by the OIDC specification to be stable and unique](https://openid.net/specs/openid-connect-core-1_0.html#ClaimStability), even if a user changes email address. A well-designed IdP will typically set `sub` to an opaque identifier like a UUID or numeric ID, which has no relation to the user's name or email address.

This issue _only_ affects Headscale installations which authenticate with OIDC.

Headscale v0.24.0 and later will also automatically update profile fields with OIDC data on login. This means that users can change those details in your IdP, and have it populate to Headscale automatically the next time they log in. However, this may affect the way you reference users in policies.

#### Migrating existing installations

Headscale v0.23.0 and earlier never recorded the `iss` and `sub` fields, so all legacy (existing) OIDC accounts from _need to be migrated_ to be properly secured.

Headscale v0.24.0 has an automatic migration feature, which is enabled by default (`map_legacy_users: true`). **This will be disabled by default in a future version of Headscale – any unmigrated users will get new accounts.**

Headscale v0.24.0 will ignore any `email` claim if the IdP does not provide an `email_verified` claim set to `true`. [What "verified" actually means is contextually dependent](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) – Headscale uses it as a signal that the contents of the `email` claim is reasonably trustworthy.

Headscale v0.23.0 and earlier never checked the `email_verified` claim. This means even if an IdP explicitly indicated to Headscale that its `email` claim was untrustworthy, Headscale would have still accepted it.

##### What does automatic migration do?

When automatic migration is enabled (`map_legacy_users: true`), Headscale will first match an OIDC account to a Headscale account by `iss` and `sub`, and then fall back to matching OIDC users similarly to how Headscale v0.23.0 did:

- If `strip_email_domain: true` (the default): the Headscale username matches the "username" part of their email address.
- If `strip_email_domain: false`: the Headscale username matches the _whole_ email address.

On migration, Headscale will change the account's username to their `preferred_username`. **This could break any ACLs or policies which are configured to match by username.**

Like with Headscale v0.23.0 and earlier, this migration only works for users who haven't changed their email address since their last Headscale login.

A _successful_ automated migration should otherwise be transparent to users.

Once a Headscale account has been migrated, it will be _unavailable_ to be matched by the legacy process. An OIDC login with a matching username, but _non-matching_ `iss` and `sub` will instead get a _new_ Headscale account.

Because of the way OIDC works, Headscale's automated migration process can _only_ work when a user tries to log in after the update. Mass updates would require Headscale implement a protocol like SCIM, which is **extremely** complicated and not available in all identity providers.

Administrators could also attempt to migrate users manually by editing the database, using their own mapping rules with known-good data sources.

Legacy account migration should have no effect on new installations where all users have a recorded `sub` and `iss`.

##### What happens when automatic migration is disabled?

When automatic migration is disabled (`map_legacy_users: false`), Headscale will only try to match an OIDC account to a Headscale account by `iss` and `sub`.

If there is no match, it will get a _new_ Headscale account – even if there was a legacy account which _could_ have matched and migrated.

We recommend new Headscale users explicitly disable automatic migration – but it should otherwise have no effect if every account has a recorded `iss` and `sub`.

When automatic migration is disabled, the `strip_email_domain` setting will have no effect.

Special thanks to @micolous for reviewing, proposing and working with us on these changes.

#### Other OIDC changes

Headscale now uses [the standard OIDC claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) to populate and update user information every time they log in:

| Headscale profile field | OIDC claim | Notes / examples |
| ----------------------- | -------------------- | --------------------------------------------------------------------------------------------------------- |
| email address | `email` | Only used when `"email_verified": true` |
| display name | `name` | eg: `Sam Smith` |
| username | `preferred_username` | Varies depending on IdP and configuration, eg: `ssmith`, `ssmith@idp.example.com`, `\\example.com\ssmith` |
| profile picture | `picture` | URL to a profile picture or avatar |

These should show up nicely in the Tailscale client.

This will also affect the way you [reference users in policies](https://github.com/juanfont/headscale/pull/2205).

### 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

Expand All @@ -22,6 +88,8 @@
- Fixed processing of fields in post request in MoveNode rpc [#2179](https://github.com/juanfont/headscale/pull/2179)
- Added conversion of 'Hostname' to 'givenName' in a node with FQDN rules applied [#2198](https://github.com/juanfont/headscale/pull/2198)
- Fixed updating of hostname and givenName when it is updated in HostInfo [#2199](https://github.com/juanfont/headscale/pull/2199)
- Fixed missing `stable-debug` container tag [#2232](https://github.com/juanfont/headscale/pr/2232)
- Loosened up `server_url` and `base_domain` check. It was overly strict in some cases.

## 0.23.0 (2024-09-18)

Expand Down
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ event.

Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported to the community leaders responsible for enforcement
at our Discord channel. All complaints
on our [Discord server](https://discord.gg/c84AZQhmpx). All complaints
will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and
Expand Down
19 changes: 19 additions & 0 deletions Dockerfile.derper
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# For testing purposes only

FROM golang:alpine AS build-env

WORKDIR /go/src

RUN apk add --no-cache git
ARG VERSION_BRANCH=main
RUN git clone https://github.com/tailscale/tailscale.git --branch=$VERSION_BRANCH --depth=1
WORKDIR /go/src/tailscale

ARG TARGETARCH
RUN GOARCH=$TARGETARCH go install -v ./cmd/derper

FROM alpine:3.18
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables curl

COPY --from=build-env /go/bin/* /usr/local/bin/
ENTRYPOINT [ "/usr/local/bin/derper" ]
4 changes: 3 additions & 1 deletion Dockerfile.tailscale-HEAD
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ ARG VERSION_GIT_HASH=""
ENV VERSION_GIT_HASH=$VERSION_GIT_HASH
ARG TARGETARCH

RUN GOARCH=$TARGETARCH go install -ldflags="\
ARG BUILD_TAGS=""

RUN GOARCH=$TARGETARCH go install -tags="${BUILD_TAGS}" -ldflags="\
-X tailscale.com/version.longStamp=$VERSION_LONG \
-X tailscale.com/version.shortStamp=$VERSION_SHORT \
-X tailscale.com/version.gitCommitStamp=$VERSION_GIT_HASH" \
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

An open source, self-hosted implementation of the Tailscale control server.

Join our [Discord](https://discord.gg/c84AZQhmpx) server for a chat.
Join our [Discord server](https://discord.gg/c84AZQhmpx) for a chat.

**Note:** Always select the same GitHub tag as the released version you use
to ensure you have the correct example configuration and documentation.
Expand Down
37 changes: 34 additions & 3 deletions cmd/headscale/cli/mockoidc.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package cli

import (
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"strconv"
"time"
Expand Down Expand Up @@ -64,14 +66,27 @@ func mockOIDC() error {
accessTTL = newTTL
}

userStr := os.Getenv("MOCKOIDC_USERS")
if userStr == "" {
return fmt.Errorf("MOCKOIDC_USERS not defined")
}

var users []mockoidc.MockUser
err := json.Unmarshal([]byte(userStr), &users)
if err != nil {
return fmt.Errorf("unmarshalling users: %w", err)
}

log.Info().Interface("users", users).Msg("loading users from JSON")

log.Info().Msgf("Access token TTL: %s", accessTTL)

port, err := strconv.Atoi(portStr)
if err != nil {
return err
}

mock, err := getMockOIDC(clientID, clientSecret)
mock, err := getMockOIDC(clientID, clientSecret, users)
if err != nil {
return err
}
Expand All @@ -93,12 +108,18 @@ func mockOIDC() error {
return nil
}

func getMockOIDC(clientID string, clientSecret string) (*mockoidc.MockOIDC, error) {
func getMockOIDC(clientID string, clientSecret string, users []mockoidc.MockUser) (*mockoidc.MockOIDC, error) {
keypair, err := mockoidc.NewKeypair(nil)
if err != nil {
return nil, err
}

userQueue := mockoidc.UserQueue{}

for _, user := range users {
userQueue.Push(&user)
}

mock := mockoidc.MockOIDC{
ClientID: clientID,
ClientSecret: clientSecret,
Expand All @@ -107,9 +128,19 @@ func getMockOIDC(clientID string, clientSecret string) (*mockoidc.MockOIDC, erro
CodeChallengeMethodsSupported: []string{"plain", "S256"},
Keypair: keypair,
SessionStore: mockoidc.NewSessionStore(),
UserQueue: &mockoidc.UserQueue{},
UserQueue: &userQueue,
ErrorQueue: &mockoidc.ErrorQueue{},
}

mock.AddMiddleware(func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Info().Msgf("Request: %+v", r)
h.ServeHTTP(w, r)
if r.Response != nil {
log.Info().Msgf("Response: %+v", r.Response)
}
})
})

return &mock, nil
}
23 changes: 17 additions & 6 deletions config-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ database:
# https://www.sqlite.org/wal.html
write_ahead_log: true

# Maximum number of WAL file frames before the WAL file is automatically checkpointed.
# https://www.sqlite.org/c3ref/wal_autocheckpoint.html
# Set to 0 to disable automatic checkpointing.
wal_autocheckpoint: 1000

# # Postgres config
# Please note that using Postgres is highly discouraged as it is only supported for legacy reasons.
# See database.type for more information.
Expand Down Expand Up @@ -364,12 +369,18 @@ unix_socket_permission: "0770"
# allowed_users:
# - alice@example.com
#
# # If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed.
# # This will transform `first-name.last-name@example.com` to the user `first-name.last-name`
# # If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following
# user: `first-name.last-name.example.com`
#
# strip_email_domain: true
# # Map legacy users from pre-0.24.0 versions of headscale to the new OIDC users
# # by taking the username from the legacy user and matching it with the username
# # provided by the OIDC. This is useful when migrating from legacy users to OIDC
# # to force them using the unique identifier from the OIDC and to give them a
# # proper display name and picture if available.
# # Note that this will only work if the username from the legacy user is the same
# # and ther is a posibility for account takeover should a username have changed
# # with the provider.
# # Disabling this feature will cause all new logins to be created as new users.
# # Note this option will be removed in the future and should be set to false
# # on all new installations, or when all users have logged in with OIDC once.
# map_legacy_users: true

# Logtail configuration
# Logtail is Tailscales logging and auditing infrastructure, it allows the control panel
Expand Down
1 change: 1 addition & 0 deletions docs/about/clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ headscale.
| Android | Yes (see [docs](../usage/connect/android.md)) |
| macOS | Yes (see [docs](../usage/connect/apple.md#macos) and `/apple` on your headscale for more information) |
| iOS | Yes (see [docs](../usage/connect/apple.md#ios) and `/apple` on your headscale for more information) |
| tvOS | Yes (see [docs](../usage/connect/apple.md#tvos) and `/apple` on your headscale for more information) |
10 changes: 6 additions & 4 deletions docs/about/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ In addition to that, you may use packages provided by the community or from dist
[installation guide using community packages](../setup/install/community.md).

For convenience, we also [build Docker images with headscale](../setup/install/container.md). But **please be aware that
we don't officially support deploying headscale using Docker**. We have a [Discord
channel](https://discord.com/channels/896711691637780480/1070619770942148618) where you can ask for Docker-specific help
to the community.
we don't officially support deploying headscale using Docker**. On our [Discord server](https://discord.gg/c84AZQhmpx)
we have a "docker-issues" channel where you can ask for Docker-specific help to the community.

## Why is my reverse proxy not working with headscale?

We don't know. We don't use reverse proxies with headscale ourselves, so we don't have any experience with them. We have [community documentation](../ref/integration/reverse-proxy.md) on how to configure various reverse proxies, and a dedicated [Discord channel](https://discord.com/channels/896711691637780480/1070619818346164324) where you can ask for help to the community.
We don't know. We don't use reverse proxies with headscale ourselves, so we don't have any experience with them. We have
[community documentation](../ref/integration/reverse-proxy.md) on how to configure various reverse proxies, and a
dedicated "reverse-proxy-issues" channel on our [Discord server](https://discord.gg/c84AZQhmpx) where you can ask for
help to the community.

## Can I use headscale and tailscale on the same machine?

Expand Down
8 changes: 1 addition & 7 deletions docs/about/help.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
# Getting help

Join our Discord server for announcements and community support:

- [announcements](https://discord.com/channels/896711691637780480/896711692120129538)
- [general](https://discord.com/channels/896711691637780480/896711692120129540)
- [docker-issues](https://discord.com/channels/896711691637780480/1070619770942148618)
- [reverse-proxy-issues](https://discord.com/channels/896711691637780480/1070619818346164324)
- [web-interfaces](https://discord.com/channels/896711691637780480/1105842846386356294)
Join our [Discord server](https://discord.gg/c84AZQhmpx) for announcements and community support.

Please report bugs via [GitHub issues](https://github.com/juanfont/headscale/issues)
3 changes: 1 addition & 2 deletions docs/about/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@ code archives. Container images are available on [Docker Hub](https://hub.docker

An Atom/RSS feed of headscale releases is available [here](https://github.com/juanfont/headscale/releases.atom).

Join the ["announcements" channel on Discord](https://discord.com/channels/896711691637780480/896711692120129538) for
news about headscale.
See the "announcements" channel on our [Discord server](https://discord.gg/c84AZQhmpx) for news about headscale.
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Headscale is an open source, self-hosted implementation of the Tailscale control

This page contains the documentation for the latest version of headscale. Please also check our [FAQ](./about/faq.md).

Join our [Discord](https://discord.gg/c84AZQhmpx) server for a chat and community support.
Join our [Discord server](https://discord.gg/c84AZQhmpx) for a chat and community support.

## Design goal

Expand Down
Loading

0 comments on commit ccc2dac

Please sign in to comment.