all: annotate gosec false positives with rationale
Each //nolint:gosec carries the gosec code and one line on why
the finding is a false positive or already mitigated.
G124 cookies (oidc.go x3, oidc_confirm_test.go)
Secure is set conditionally on req.TLS != nil; HttpOnly and
SameSiteStrictMode already on. gosec misses the conditional.
Test fixture cookie is explicitly a test fixture.
G705 (debug.go)
templates.PingPage(...).Render() is a templ component that
auto-escapes user input.
G706 (scenario.go)
Integration log emits trusted scenario state. The pre-built
image G706 sites in hsic.go / tsic.go ride along with the
earlier constants commit.
G710 (app.go, tailsql.go)
Redirect target is "trusted ServerURL prefix + path". gosec
cannot see past the prefix.
This commit is contained in:
parent
f905d58292
commit
3e2aa5814e
@ -274,7 +274,7 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) {
|
|||||||
// Redirect to our TLS url.
|
// Redirect to our TLS url.
|
||||||
func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
|
func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
|
||||||
target := h.cfg.ServerURL + req.URL.RequestURI()
|
target := h.cfg.ServerURL + req.URL.RequestURI()
|
||||||
http.Redirect(w, req, target, http.StatusFound)
|
http.Redirect(w, req, target, http.StatusFound) //nolint:gosec // G710: target prefixed by trusted ServerURL
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) scheduledTasks(ctx context.Context) {
|
func (h *Headscale) scheduledTasks(ctx context.Context) {
|
||||||
@ -580,7 +580,7 @@ func (h *Headscale) createRouter(grpcMux *grpcRuntime.ServeMux) *chi.Mux {
|
|||||||
r.HandleFunc("/v1/*", grpcMux.ServeHTTP)
|
r.HandleFunc("/v1/*", grpcMux.ServeHTTP)
|
||||||
})
|
})
|
||||||
// Ping response endpoint: receives HEAD from clients responding
|
// Ping response endpoint: receives HEAD from clients responding
|
||||||
// to a PingRequest. The unguessable ping ID serves as authentication.
|
// to a [tailcfg.PingRequest]. The unguessable ping ID serves as authentication.
|
||||||
r.Head("/machine/ping-response", h.PingResponseHandler)
|
r.Head("/machine/ping-response", h.PingResponseHandler)
|
||||||
|
|
||||||
r.Get("/favicon.ico", FaviconHandler)
|
r.Get("/favicon.ico", FaviconHandler)
|
||||||
@ -1144,7 +1144,7 @@ func (h *Headscale) Change(cs ...change.Change) {
|
|||||||
h.mapBatcher.AddWork(cs...)
|
h.mapBatcher.AddWork(cs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPHandler returns an http.Handler for the Headscale control server.
|
// HTTPHandler returns an [http.Handler] for the [Headscale] control server.
|
||||||
// The handler serves the Tailscale control protocol including the /key
|
// The handler serves the Tailscale control protocol including the /key
|
||||||
// endpoint and /ts2021 Noise upgrade path.
|
// endpoint and /ts2021 Noise upgrade path.
|
||||||
func (h *Headscale) HTTPHandler() http.Handler {
|
func (h *Headscale) HTTPHandler() http.Handler {
|
||||||
@ -1224,7 +1224,7 @@ func (l *acmeLogger) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// zerologRequestLogger implements chi's middleware.LogFormatter
|
// [zerologRequestLogger] implements chi's [middleware.LogFormatter]
|
||||||
// to route HTTP request logs through zerolog.
|
// to route HTTP request logs through zerolog.
|
||||||
type zerologRequestLogger struct{}
|
type zerologRequestLogger struct{}
|
||||||
|
|
||||||
|
|||||||
@ -27,15 +27,15 @@ const (
|
|||||||
defaultOAuthOptionsCount = 3
|
defaultOAuthOptionsCount = 3
|
||||||
authCacheExpiration = time.Minute * 15
|
authCacheExpiration = time.Minute * 15
|
||||||
|
|
||||||
// authCacheMaxEntries bounds the OIDC state→AuthInfo cache to prevent
|
// authCacheMaxEntries bounds the OIDC state→[AuthInfo] cache to prevent
|
||||||
// unauthenticated cache-fill DoS via repeated /register/{auth_id} or
|
// unauthenticated cache-fill DoS via repeated /register/{auth_id} or
|
||||||
// /auth/{auth_id} GETs that mint OIDC state cookies.
|
// /auth/{auth_id} GETs that mint OIDC state cookies.
|
||||||
authCacheMaxEntries = 1024
|
authCacheMaxEntries = 1024
|
||||||
|
|
||||||
// cookieNamePrefixLen is the number of leading characters from a
|
// cookieNamePrefixLen is the number of leading characters from a
|
||||||
// state/nonce value that getCookieName splices into the cookie name.
|
// state/nonce value that [getCookieName] splices into the cookie name.
|
||||||
// State and nonce values that are shorter than this are rejected at
|
// State and nonce values that are shorter than this are rejected at
|
||||||
// the callback boundary so getCookieName cannot panic on a slice
|
// the callback boundary so [getCookieName] cannot panic on a slice
|
||||||
// out-of-range.
|
// out-of-range.
|
||||||
cookieNamePrefixLen = 6
|
cookieNamePrefixLen = 6
|
||||||
)
|
)
|
||||||
@ -69,7 +69,7 @@ type AuthProviderOIDC struct {
|
|||||||
cfg *types.OIDCConfig
|
cfg *types.OIDCConfig
|
||||||
|
|
||||||
// authCache holds auth information between the auth and the callback
|
// authCache holds auth information between the auth and the callback
|
||||||
// steps. It is a bounded LRU keyed by OIDC state, evicting oldest
|
// steps. It is a bounded [expirable.LRU] keyed by OIDC state, evicting oldest
|
||||||
// entries to keep the cache footprint constant under attack.
|
// entries to keep the cache footprint constant under attack.
|
||||||
authCache *expirable.LRU[string, AuthInfo]
|
authCache *expirable.LRU[string, AuthInfo]
|
||||||
|
|
||||||
@ -286,9 +286,9 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
|
|||||||
util.LogErr(err, "could not get userinfo; only using claims from id token")
|
util.LogErr(err, "could not get userinfo; only using claims from id token")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The oidc.UserInfo type only decodes some fields (Subject, Profile, Email, EmailVerified).
|
// The [oidc.UserInfo] type only decodes some fields (Subject, Profile, Email, EmailVerified).
|
||||||
// We are interested in other fields too (e.g. groups are required for allowedGroups) so we
|
// We are interested in other fields too (e.g. groups are required for allowedGroups) so we
|
||||||
// decode into our own OIDCUserInfo type using the underlying claims struct.
|
// decode into our own [types.OIDCUserInfo] type using the underlying claims struct.
|
||||||
var userinfo2 types.OIDCUserInfo
|
var userinfo2 types.OIDCUserInfo
|
||||||
if userinfo != nil && userinfo.Claims(&userinfo2) == nil && userinfo2.Sub == claims.Sub {
|
if userinfo != nil && userinfo.Claims(&userinfo2) == nil && userinfo2.Sub == claims.Sub {
|
||||||
// Update the user with the userinfo claims (with id token claims as fallback).
|
// Update the user with the userinfo claims (with id token claims as fallback).
|
||||||
@ -444,10 +444,10 @@ func extractCodeAndStateParamFromRequest(
|
|||||||
return "", "", NewHTTPError(http.StatusBadRequest, "missing code or state parameter", errEmptyOIDCCallbackParams)
|
return "", "", NewHTTPError(http.StatusBadRequest, "missing code or state parameter", errEmptyOIDCCallbackParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reject states that are too short for getCookieName to splice
|
// Reject states that are too short for [getCookieName] to splice
|
||||||
// into a cookie name. Without this guard a request with
|
// into a cookie name. Without this guard a request with
|
||||||
// ?state=abc panics on the slice out-of-range and is recovered by
|
// ?state=abc panics on the slice out-of-range and is recovered by
|
||||||
// chi's middleware.Recoverer, amplifying small-DoS log noise.
|
// chi's [middleware.Recoverer], amplifying small-DoS log noise.
|
||||||
if len(state) < cookieNamePrefixLen {
|
if len(state) < cookieNamePrefixLen {
|
||||||
return "", "", NewHTTPError(http.StatusBadRequest, "invalid state parameter", errOIDCStateTooShort)
|
return "", "", NewHTTPError(http.StatusBadRequest, "invalid state parameter", errOIDCStateTooShort)
|
||||||
}
|
}
|
||||||
@ -552,15 +552,15 @@ func validateOIDCAllowedUsers(
|
|||||||
//
|
//
|
||||||
// The following tests are always applied:
|
// The following tests are always applied:
|
||||||
//
|
//
|
||||||
// - validateOIDCAllowedGroups
|
// - [validateOIDCAllowedGroups]
|
||||||
//
|
//
|
||||||
// The following tests are applied if cfg.EmailVerifiedRequired=false
|
// The following tests are applied if cfg.EmailVerifiedRequired=false
|
||||||
// or claims.email_verified=true:
|
// or claims.email_verified=true:
|
||||||
//
|
//
|
||||||
// - validateOIDCAllowedDomains
|
// - [validateOIDCAllowedDomains]
|
||||||
// - validateOIDCAllowedUsers
|
// - [validateOIDCAllowedUsers]
|
||||||
//
|
//
|
||||||
// NOTE that, contrary to the function name, validateOIDCAllowedUsers
|
// NOTE that, contrary to the function name, [validateOIDCAllowedUsers]
|
||||||
// only checks the email address -- not the username.
|
// only checks the email address -- not the username.
|
||||||
func doOIDCAuthorization(
|
func doOIDCAuthorization(
|
||||||
cfg *types.OIDCConfig,
|
cfg *types.OIDCConfig,
|
||||||
@ -658,7 +658,7 @@ func (a *AuthProviderOIDC) createOrUpdateUserFromClaim(
|
|||||||
const registerConfirmCSRFCookie = "headscale_register_confirm"
|
const registerConfirmCSRFCookie = "headscale_register_confirm"
|
||||||
|
|
||||||
// renderRegistrationConfirmInterstitial captures the resolved OIDC
|
// renderRegistrationConfirmInterstitial captures the resolved OIDC
|
||||||
// identity and node expiry into the cached AuthRequest, sets the CSRF
|
// identity and node expiry into the cached [types.AuthRequest], sets the CSRF
|
||||||
// cookie, and renders the confirmation page that the user must
|
// cookie, and renders the confirmation page that the user must
|
||||||
// explicitly submit before the registration is finalised.
|
// explicitly submit before the registration is finalised.
|
||||||
func (a *AuthProviderOIDC) renderRegistrationConfirmInterstitial(
|
func (a *AuthProviderOIDC) renderRegistrationConfirmInterstitial(
|
||||||
@ -698,6 +698,7 @@ func (a *AuthProviderOIDC) renderRegistrationConfirmInterstitial(
|
|||||||
CSRF: csrf,
|
CSRF: csrf,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//nolint:gosec // G124: Secure set conditionally via req.TLS; HttpOnly + SameSite already set
|
||||||
http.SetCookie(writer, &http.Cookie{
|
http.SetCookie(writer, &http.Cookie{
|
||||||
Name: registerConfirmCSRFCookie,
|
Name: registerConfirmCSRFCookie,
|
||||||
Value: csrf,
|
Value: csrf,
|
||||||
@ -733,7 +734,7 @@ func (a *AuthProviderOIDC) renderRegistrationConfirmInterstitial(
|
|||||||
// RegisterConfirmHandler is the POST endpoint behind the OIDC
|
// RegisterConfirmHandler is the POST endpoint behind the OIDC
|
||||||
// registration confirmation interstitial. It validates the CSRF cookie
|
// registration confirmation interstitial. It validates the CSRF cookie
|
||||||
// against the form-submitted token, finalises the registration via
|
// against the form-submitted token, finalises the registration via
|
||||||
// handleRegistration, and renders the success page.
|
// [AuthProviderOIDC.handleRegistration], and renders the success page.
|
||||||
func (a *AuthProviderOIDC) RegisterConfirmHandler(
|
func (a *AuthProviderOIDC) RegisterConfirmHandler(
|
||||||
writer http.ResponseWriter,
|
writer http.ResponseWriter,
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
@ -823,6 +824,7 @@ func (a *AuthProviderOIDC) RegisterConfirmHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear the CSRF cookie now that the registration is final.
|
// Clear the CSRF cookie now that the registration is final.
|
||||||
|
//nolint:gosec // G124: Secure set conditionally via req.TLS; HttpOnly + SameSite already set
|
||||||
http.SetCookie(writer, &http.Cookie{
|
http.SetCookie(writer, &http.Cookie{
|
||||||
Name: registerConfirmCSRFCookie,
|
Name: registerConfirmCSRFCookie,
|
||||||
Value: "",
|
Value: "",
|
||||||
@ -838,7 +840,7 @@ func (a *AuthProviderOIDC) RegisterConfirmHandler(
|
|||||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
writer.WriteHeader(http.StatusOK)
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
// renderRegistrationSuccessTemplate's output only embeds
|
// [renderRegistrationSuccessTemplate]'s output only embeds
|
||||||
// HTML-escaped values from a server-side template, so the gosec
|
// HTML-escaped values from a server-side template, so the gosec
|
||||||
// XSS warning is a false positive here.
|
// XSS warning is a false positive here.
|
||||||
if _, err := writer.Write(content.Bytes()); err != nil { //nolint:noinlineerr,gosec
|
if _, err := writer.Write(content.Bytes()); err != nil { //nolint:noinlineerr,gosec
|
||||||
@ -918,9 +920,9 @@ func renderAuthSuccessTemplate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getCookieName generates a unique cookie name based on a cookie value.
|
// getCookieName generates a unique cookie name based on a cookie value.
|
||||||
// Callers must ensure value has at least cookieNamePrefixLen bytes;
|
// Callers must ensure value has at least [cookieNamePrefixLen] bytes;
|
||||||
// extractCodeAndStateParamFromRequest enforces this for the state
|
// [extractCodeAndStateParamFromRequest] enforces this for the state
|
||||||
// parameter, and setCSRFCookie always supplies a 64-byte random value.
|
// parameter, and [setCSRFCookie] always supplies a 64-byte random value.
|
||||||
func getCookieName(baseName, value string) string {
|
func getCookieName(baseName, value string) string {
|
||||||
return fmt.Sprintf("%s_%s", baseName, value[:cookieNamePrefixLen])
|
return fmt.Sprintf("%s_%s", baseName, value[:cookieNamePrefixLen])
|
||||||
}
|
}
|
||||||
@ -931,6 +933,7 @@ func setCSRFCookie(w http.ResponseWriter, r *http.Request, name string) (string,
|
|||||||
return val, err
|
return val, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gosec // G124: Secure set conditionally via r.TLS; HttpOnly + SameSite already set
|
||||||
c := &http.Cookie{
|
c := &http.Cookie{
|
||||||
Path: "/oidc/callback",
|
Path: "/oidc/callback",
|
||||||
Name: getCookieName(name, val),
|
Name: getCookieName(name, val),
|
||||||
|
|||||||
@ -24,6 +24,7 @@ func newConfirmRequest(t *testing.T, authID types.AuthID, formCSRF, cookieCSRF s
|
|||||||
form,
|
form,
|
||||||
)
|
)
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
//nolint:gosec // G124: test fixture
|
||||||
req.AddCookie(&http.Cookie{
|
req.AddCookie(&http.Cookie{
|
||||||
Name: registerConfirmCSRFCookie,
|
Name: registerConfirmCSRFCookie,
|
||||||
Value: cookieCSRF,
|
Value: cookieCSRF,
|
||||||
@ -67,7 +68,7 @@ func TestRegisterConfirmHandler_RejectsCSRFMismatch(t *testing.T) {
|
|||||||
"CSRF cookie/form mismatch must be rejected with 403")
|
"CSRF cookie/form mismatch must be rejected with 403")
|
||||||
|
|
||||||
// And the registration must still be pending — the rejected POST
|
// And the registration must still be pending — the rejected POST
|
||||||
// must not have called handleRegistration.
|
// must not have called [AuthProviderOIDC.handleRegistration].
|
||||||
cached, ok := app.state.GetAuthCacheEntry(authID)
|
cached, ok := app.state.GetAuthCacheEntry(authID)
|
||||||
require.True(t, ok, "rejected POST must not evict the cached registration")
|
require.True(t, ok, "rejected POST must not evict the cached registration")
|
||||||
require.NotNil(t, cached.PendingConfirmation(),
|
require.NotNil(t, cached.PendingConfirmation(),
|
||||||
|
|||||||
@ -84,7 +84,7 @@ func runTailSQLService(ctx context.Context, logf logger.Logf, stateDir, dbPath s
|
|||||||
go func() {
|
go func() {
|
||||||
_ = http.Serve(lst, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //nolint:gosec
|
_ = http.Serve(lst, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //nolint:gosec
|
||||||
target := base + r.RequestURI
|
target := base + r.RequestURI
|
||||||
http.Redirect(w, r, target, http.StatusPermanentRedirect)
|
http.Redirect(w, r, target, http.StatusPermanentRedirect) //nolint:gosec // G710: target prefixed by trusted base URL
|
||||||
}))
|
}))
|
||||||
}()
|
}()
|
||||||
// log.Printf("Redirecting HTTP to HTTPS at %q", base)
|
// log.Printf("Redirecting HTTP to HTTPS at %q", base)
|
||||||
|
|||||||
@ -56,7 +56,7 @@ var (
|
|||||||
errNoClientFound = errors.New("client not found")
|
errNoClientFound = errors.New("client not found")
|
||||||
|
|
||||||
// AllVersions represents a list of Tailscale versions the suite
|
// AllVersions represents a list of Tailscale versions the suite
|
||||||
// uses to test compatibility with the ControlServer.
|
// uses to test compatibility with the [ControlServer].
|
||||||
//
|
//
|
||||||
// The list contains two special cases, "head" and "unstable" which
|
// The list contains two special cases, "head" and "unstable" which
|
||||||
// points to the current tip of Tailscale's main branch and the latest
|
// points to the current tip of Tailscale's main branch and the latest
|
||||||
@ -78,7 +78,7 @@ var (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// User represents a User in the ControlServer and a map of TailscaleClient's
|
// User represents a User in the [ControlServer] and a map of [TailscaleClient]'s
|
||||||
// associated with the User.
|
// associated with the User.
|
||||||
type User struct {
|
type User struct {
|
||||||
Clients map[string]TailscaleClient
|
Clients map[string]TailscaleClient
|
||||||
@ -88,10 +88,10 @@ type User struct {
|
|||||||
syncWaitGroup errgroup.Group
|
syncWaitGroup errgroup.Group
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scenario is a representation of an environment with one ControlServer and
|
// Scenario is a representation of an environment with one [ControlServer] and
|
||||||
// one or more User's and its associated TailscaleClients.
|
// one or more [User]'s and its associated [TailscaleClient]s.
|
||||||
// A Scenario is intended to simplify setting up a new testcase for testing
|
// A Scenario is intended to simplify setting up a new testcase for testing
|
||||||
// a ControlServer with TailscaleClients.
|
// a [ControlServer] with [TailscaleClient]s.
|
||||||
// TODO(kradalby): make control server configurable, test correctness with Tailscale SaaS.
|
// TODO(kradalby): make control server configurable, test correctness with Tailscale SaaS.
|
||||||
type Scenario struct {
|
type Scenario struct {
|
||||||
// TODO(kradalby): support multiple headcales for later, currently only
|
// TODO(kradalby): support multiple headcales for later, currently only
|
||||||
@ -177,8 +177,8 @@ func (s *Scenario) prefixedNetworkName(name string) string {
|
|||||||
return s.testHashPrefix + "-" + name
|
return s.testHashPrefix + "-" + name
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScenario creates a test Scenario which can be used to bootstraps a ControlServer with
|
// NewScenario creates a test [Scenario] which can be used to bootstraps a [ControlServer] with
|
||||||
// a set of Users and TailscaleClients.
|
// a set of [User]s and [TailscaleClient]s.
|
||||||
func NewScenario(spec ScenarioSpec) (*Scenario, error) {
|
func NewScenario(spec ScenarioSpec) (*Scenario, error) {
|
||||||
pool, err := dockertest.NewPool("")
|
pool, err := dockertest.NewPool("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -422,15 +422,15 @@ func (s *Scenario) ShutdownAssertNoPanics(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown shuts down and cleans up all the containers (ControlServer, TailscaleClient)
|
// Shutdown shuts down and cleans up all the containers ([ControlServer], [TailscaleClient])
|
||||||
// and networks associated with it.
|
// and networks associated with it.
|
||||||
// In addition, it will save the logs of the ControlServer to `/tmp/control` in the
|
// In addition, it will save the logs of the [ControlServer] to `/tmp/control` in the
|
||||||
// environment running the tests.
|
// environment running the tests.
|
||||||
func (s *Scenario) Shutdown() {
|
func (s *Scenario) Shutdown() {
|
||||||
s.ShutdownAssertNoPanics(nil)
|
s.ShutdownAssertNoPanics(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Users returns the name of all users associated with the Scenario.
|
// Users returns the name of all users associated with the [Scenario].
|
||||||
func (s *Scenario) Users() []string {
|
func (s *Scenario) Users() []string {
|
||||||
users := make([]string, 0, len(s.users))
|
users := make([]string, 0, len(s.users))
|
||||||
for user := range s.users {
|
for user := range s.users {
|
||||||
@ -443,8 +443,8 @@ func (s *Scenario) Users() []string {
|
|||||||
/// Headscale related stuff
|
/// Headscale related stuff
|
||||||
// Note: These functions assume that there is a _single_ headscale instance for now
|
// Note: These functions assume that there is a _single_ headscale instance for now
|
||||||
|
|
||||||
// Headscale returns a ControlServer instance based on hsic (HeadscaleInContainer)
|
// Headscale returns a [ControlServer] instance based on hsic ([hsic.HeadscaleInContainer]).
|
||||||
// If the Scenario already has an instance, the pointer to the running container
|
// If the [Scenario] already has an instance, the pointer to the running container
|
||||||
// will be return, otherwise a new instance will be created.
|
// will be return, otherwise a new instance will be created.
|
||||||
// TODO(kradalby): make port and headscale configurable, multiple instances support?
|
// TODO(kradalby): make port and headscale configurable, multiple instances support?
|
||||||
func (s *Scenario) Headscale(opts ...hsic.Option) (ControlServer, error) {
|
func (s *Scenario) Headscale(opts ...hsic.Option) (ControlServer, error) {
|
||||||
@ -474,12 +474,12 @@ func (s *Scenario) Headscale(opts ...hsic.Option) (ControlServer, error) {
|
|||||||
return headscale, nil
|
return headscale, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pool returns the dockertest pool for the scenario.
|
// Pool returns the [dockertest.Pool] for the scenario.
|
||||||
func (s *Scenario) Pool() *dockertest.Pool {
|
func (s *Scenario) Pool() *dockertest.Pool {
|
||||||
return s.pool
|
return s.pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrCreateUser gets or creates a user in the scenario.
|
// GetOrCreateUser gets or creates a user in the [Scenario].
|
||||||
func (s *Scenario) GetOrCreateUser(userStr string) *User {
|
func (s *Scenario) GetOrCreateUser(userStr string) *User {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
@ -497,7 +497,7 @@ func (s *Scenario) GetOrCreateUser(userStr string) *User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreatePreAuthKey creates a "pre authentorised key" to be created in the
|
// CreatePreAuthKey creates a "pre authentorised key" to be created in the
|
||||||
// Headscale instance on behalf of the Scenario.
|
// Headscale instance on behalf of the [Scenario].
|
||||||
func (s *Scenario) CreatePreAuthKey(
|
func (s *Scenario) CreatePreAuthKey(
|
||||||
user uint64,
|
user uint64,
|
||||||
reusable bool,
|
reusable bool,
|
||||||
@ -516,7 +516,7 @@ func (s *Scenario) CreatePreAuthKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreatePreAuthKeyWithOptions creates a "pre authorised key" with the specified options
|
// CreatePreAuthKeyWithOptions creates a "pre authorised key" with the specified options
|
||||||
// to be created in the Headscale instance on behalf of the Scenario.
|
// to be created in the Headscale instance on behalf of the [Scenario].
|
||||||
func (s *Scenario) CreatePreAuthKeyWithOptions(opts hsic.AuthKeyOptions) (*v1.PreAuthKey, error) {
|
func (s *Scenario) CreatePreAuthKeyWithOptions(opts hsic.AuthKeyOptions) (*v1.PreAuthKey, error) {
|
||||||
headscale, err := s.Headscale()
|
headscale, err := s.Headscale()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -532,7 +532,7 @@ func (s *Scenario) CreatePreAuthKeyWithOptions(opts hsic.AuthKeyOptions) (*v1.Pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreatePreAuthKeyWithTags creates a "pre authorised key" with the specified tags
|
// CreatePreAuthKeyWithTags creates a "pre authorised key" with the specified tags
|
||||||
// to be created in the Headscale instance on behalf of the Scenario.
|
// to be created in the Headscale instance on behalf of the [Scenario].
|
||||||
func (s *Scenario) CreatePreAuthKeyWithTags(
|
func (s *Scenario) CreatePreAuthKeyWithTags(
|
||||||
user uint64,
|
user uint64,
|
||||||
reusable bool,
|
reusable bool,
|
||||||
@ -552,8 +552,8 @@ func (s *Scenario) CreatePreAuthKeyWithTags(
|
|||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser creates a User to be created in the
|
// CreateUser creates a [User] to be created in the
|
||||||
// Headscale instance on behalf of the Scenario.
|
// Headscale instance on behalf of the [Scenario].
|
||||||
func (s *Scenario) CreateUser(user string) (*v1.User, error) {
|
func (s *Scenario) CreateUser(user string) (*v1.User, error) {
|
||||||
if headscale, err := s.Headscale(); err == nil { //nolint:noinlineerr
|
if headscale, err := s.Headscale(); err == nil { //nolint:noinlineerr
|
||||||
u, err := headscale.CreateUser(user)
|
u, err := headscale.CreateUser(user)
|
||||||
@ -619,8 +619,8 @@ func (s *Scenario) CreateTailscaleNode(
|
|||||||
return tsClient, nil
|
return tsClient, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTailscaleNodesInUser creates and adds a new TailscaleClient to a
|
// CreateTailscaleNodesInUser creates and adds a new [TailscaleClient] to a
|
||||||
// User in the Scenario.
|
// [User] in the [Scenario].
|
||||||
func (s *Scenario) CreateTailscaleNodesInUser(
|
func (s *Scenario) CreateTailscaleNodesInUser(
|
||||||
userStr string,
|
userStr string,
|
||||||
requestedVersion string,
|
requestedVersion string,
|
||||||
@ -720,8 +720,8 @@ func (s *Scenario) CreateTailscaleNodesInUser(
|
|||||||
return fmt.Errorf("adding tailscale node: %w", errNoUserAvailable)
|
return fmt.Errorf("adding tailscale node: %w", errNoUserAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunTailscaleUp will log in all of the TailscaleClients associated with a
|
// RunTailscaleUp will log in all of the [TailscaleClient]s associated with a
|
||||||
// User to the given ControlServer (by URL).
|
// [User] to the given [ControlServer] (by URL).
|
||||||
func (s *Scenario) RunTailscaleUp(
|
func (s *Scenario) RunTailscaleUp(
|
||||||
userStr, loginServer, authKey string,
|
userStr, loginServer, authKey string,
|
||||||
) error {
|
) error {
|
||||||
@ -752,7 +752,7 @@ func (s *Scenario) RunTailscaleUp(
|
|||||||
return fmt.Errorf("bringing up tailscale node: %w", errNoUserAvailable)
|
return fmt.Errorf("bringing up tailscale node: %w", errNoUserAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountTailscale returns the total number of TailscaleClients in a Scenario.
|
// CountTailscale returns the total number of [TailscaleClient]s in a [Scenario].
|
||||||
// This is the sum of Users x TailscaleClients.
|
// This is the sum of Users x TailscaleClients.
|
||||||
func (s *Scenario) CountTailscale() int {
|
func (s *Scenario) CountTailscale() int {
|
||||||
count := 0
|
count := 0
|
||||||
@ -764,8 +764,8 @@ func (s *Scenario) CountTailscale() int {
|
|||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitForTailscaleSync blocks execution until all the TailscaleClient reports
|
// WaitForTailscaleSync blocks execution until all the [TailscaleClient] reports
|
||||||
// to have all other TailscaleClients present in their netmap.NetworkMap.
|
// to have all other [TailscaleClient]s present in their [netmap.NetworkMap].
|
||||||
func (s *Scenario) WaitForTailscaleSync() error {
|
func (s *Scenario) WaitForTailscaleSync() error {
|
||||||
tsCount := s.CountTailscale()
|
tsCount := s.CountTailscale()
|
||||||
|
|
||||||
@ -784,7 +784,7 @@ func (s *Scenario) WaitForTailscaleSync() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncOption configures WaitForTailscaleSyncPerUser.
|
// SyncOption configures [Scenario.WaitForTailscaleSyncPerUser].
|
||||||
type SyncOption func(*syncOptions)
|
type SyncOption func(*syncOptions)
|
||||||
|
|
||||||
type syncOptions struct {
|
type syncOptions struct {
|
||||||
@ -799,7 +799,7 @@ func WithPreBarrier(barrier func(context.Context) error) SyncOption {
|
|||||||
return func(o *syncOptions) { o.preBarrier = barrier }
|
return func(o *syncOptions) { o.preBarrier = barrier }
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitForTailscaleSyncPerUser blocks until each TailscaleClient has
|
// WaitForTailscaleSyncPerUser blocks until each [TailscaleClient] has
|
||||||
// the expected per-user peer count (necessary for policies like
|
// the expected per-user peer count (necessary for policies like
|
||||||
// autogroup:self where cross-user peers are invisible).
|
// autogroup:self where cross-user peers are invisible).
|
||||||
func (s *Scenario) WaitForTailscaleSyncPerUser(timeout, retryInterval time.Duration, opts ...SyncOption) error {
|
func (s *Scenario) WaitForTailscaleSyncPerUser(timeout, retryInterval time.Duration, opts ...SyncOption) error {
|
||||||
@ -848,8 +848,8 @@ func (s *Scenario) WaitForTailscaleSyncPerUser(timeout, retryInterval time.Durat
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitForTailscaleSyncWithPeerCount blocks execution until all the TailscaleClient reports
|
// WaitForTailscaleSyncWithPeerCount blocks execution until all the [TailscaleClient] reports
|
||||||
// to have all other TailscaleClients present in their netmap.NetworkMap.
|
// to have all other [TailscaleClient]s present in their [netmap.NetworkMap].
|
||||||
func (s *Scenario) WaitForTailscaleSyncWithPeerCount(peerCount int, timeout, retryInterval time.Duration) error {
|
func (s *Scenario) WaitForTailscaleSyncWithPeerCount(peerCount int, timeout, retryInterval time.Duration) error {
|
||||||
var allErrors []error
|
var allErrors []error
|
||||||
|
|
||||||
@ -890,7 +890,7 @@ func (s *Scenario) CreateHeadscaleEnv(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateHeadscaleEnv starts the headscale environment and the clients
|
// CreateHeadscaleEnv starts the headscale environment and the clients
|
||||||
// according to the ScenarioSpec passed to the Scenario.
|
// according to the [ScenarioSpec] passed to the [Scenario].
|
||||||
func (s *Scenario) createHeadscaleEnv(
|
func (s *Scenario) createHeadscaleEnv(
|
||||||
withURL bool,
|
withURL bool,
|
||||||
tsOpts []tsic.Option,
|
tsOpts []tsic.Option,
|
||||||
@ -900,7 +900,7 @@ func (s *Scenario) createHeadscaleEnv(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createHeadscaleEnvWithTags starts the headscale environment and the clients
|
// createHeadscaleEnvWithTags starts the headscale environment and the clients
|
||||||
// according to the ScenarioSpec passed to the Scenario. If preAuthKeyTags is
|
// according to the [ScenarioSpec] passed to the [Scenario]. If preAuthKeyTags is
|
||||||
// non-empty and withURL is false, the tags will be applied to the PreAuthKey
|
// non-empty and withURL is false, the tags will be applied to the PreAuthKey
|
||||||
// (tags-as-identity model).
|
// (tags-as-identity model).
|
||||||
//
|
//
|
||||||
@ -1381,6 +1381,7 @@ func (t LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gosec // G706: integration-only log of trusted scenario state
|
||||||
log.Printf(`
|
log.Printf(`
|
||||||
---
|
---
|
||||||
%s - method: %s | url: %s
|
%s - method: %s | url: %s
|
||||||
@ -1391,8 +1392,8 @@ func (t LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIPs returns all netip.Addr of TailscaleClients associated with a User
|
// GetIPs returns all [netip.Addr] of [TailscaleClient]s associated with a [User]
|
||||||
// in a Scenario.
|
// in a [Scenario].
|
||||||
func (s *Scenario) GetIPs(user string) ([]netip.Addr, error) {
|
func (s *Scenario) GetIPs(user string) ([]netip.Addr, error) {
|
||||||
var ips []netip.Addr
|
var ips []netip.Addr
|
||||||
|
|
||||||
@ -1412,7 +1413,7 @@ func (s *Scenario) GetIPs(user string) ([]netip.Addr, error) {
|
|||||||
return ips, fmt.Errorf("getting IPs: %w", errNoUserAvailable)
|
return ips, fmt.Errorf("getting IPs: %w", errNoUserAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClients returns all TailscaleClients associated with a User in a Scenario.
|
// GetClients returns all [TailscaleClient]s associated with a [User] in a [Scenario].
|
||||||
func (s *Scenario) GetClients(user string) ([]TailscaleClient, error) {
|
func (s *Scenario) GetClients(user string) ([]TailscaleClient, error) {
|
||||||
var clients []TailscaleClient
|
var clients []TailscaleClient
|
||||||
|
|
||||||
@ -1427,7 +1428,7 @@ func (s *Scenario) GetClients(user string) ([]TailscaleClient, error) {
|
|||||||
return clients, fmt.Errorf("getting clients: %w", errNoUserAvailable)
|
return clients, fmt.Errorf("getting clients: %w", errNoUserAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTailscaleClients returns a list of TailscaleClients given the Users
|
// ListTailscaleClients returns a list of [TailscaleClient]s given the [User]s
|
||||||
// passed as parameters.
|
// passed as parameters.
|
||||||
func (s *Scenario) ListTailscaleClients(users ...string) ([]TailscaleClient, error) {
|
func (s *Scenario) ListTailscaleClients(users ...string) ([]TailscaleClient, error) {
|
||||||
var allClients []TailscaleClient
|
var allClients []TailscaleClient
|
||||||
@ -1448,7 +1449,7 @@ func (s *Scenario) ListTailscaleClients(users ...string) ([]TailscaleClient, err
|
|||||||
return allClients, nil
|
return allClients, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindTailscaleClientByIP returns a TailscaleClient associated with an IP address
|
// FindTailscaleClientByIP returns a [TailscaleClient] associated with an IP address
|
||||||
// if it exists.
|
// if it exists.
|
||||||
func (s *Scenario) FindTailscaleClientByIP(ip netip.Addr) (TailscaleClient, error) {
|
func (s *Scenario) FindTailscaleClientByIP(ip netip.Addr) (TailscaleClient, error) {
|
||||||
clients, err := s.ListTailscaleClients()
|
clients, err := s.ListTailscaleClients()
|
||||||
@ -1466,7 +1467,7 @@ func (s *Scenario) FindTailscaleClientByIP(ip netip.Addr) (TailscaleClient, erro
|
|||||||
return nil, errNoClientFound
|
return nil, errNoClientFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTailscaleClientsIPs returns a list of netip.Addr based on Users
|
// ListTailscaleClientsIPs returns a list of [netip.Addr] based on [User]s
|
||||||
// passed as parameters.
|
// passed as parameters.
|
||||||
func (s *Scenario) ListTailscaleClientsIPs(users ...string) ([]netip.Addr, error) {
|
func (s *Scenario) ListTailscaleClientsIPs(users ...string) ([]netip.Addr, error) {
|
||||||
var allIps []netip.Addr
|
var allIps []netip.Addr
|
||||||
@ -1487,7 +1488,7 @@ func (s *Scenario) ListTailscaleClientsIPs(users ...string) ([]netip.Addr, error
|
|||||||
return allIps, nil
|
return allIps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTailscaleClientsFQDNs returns a list of FQDN based on Users
|
// ListTailscaleClientsFQDNs returns a list of FQDN based on [User]s
|
||||||
// passed as parameters.
|
// passed as parameters.
|
||||||
func (s *Scenario) ListTailscaleClientsFQDNs(users ...string) ([]string, error) {
|
func (s *Scenario) ListTailscaleClientsFQDNs(users ...string) ([]string, error) {
|
||||||
allFQDNs := make([]string, 0)
|
allFQDNs := make([]string, 0)
|
||||||
@ -1509,8 +1510,8 @@ func (s *Scenario) ListTailscaleClientsFQDNs(users ...string) ([]string, error)
|
|||||||
return allFQDNs, nil
|
return allFQDNs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitForTailscaleLogout blocks execution until all TailscaleClients have
|
// WaitForTailscaleLogout blocks execution until all [TailscaleClient]s have
|
||||||
// logged out of the ControlServer.
|
// logged out of the [ControlServer].
|
||||||
func (s *Scenario) WaitForTailscaleLogout() error {
|
func (s *Scenario) WaitForTailscaleLogout() error {
|
||||||
for _, user := range s.users {
|
for _, user := range s.users {
|
||||||
for _, client := range user.Clients {
|
for _, client := range user.Clients {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user