Update
This commit is contained in:
29
vendor/tailscale.com/internal/client/tailscale/identityfederation.go
generated
vendored
Normal file
29
vendor/tailscale.com/internal/client/tailscale/identityfederation.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package tailscale
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tailscale.com/feature"
|
||||
)
|
||||
|
||||
// HookResolveAuthKeyViaWIF resolves to [identityfederation.resolveAuthKey] when the
|
||||
// corresponding feature tag is enabled in the build process.
|
||||
//
|
||||
// baseURL is the URL of the control server used for token exchange and authkey generation.
|
||||
// clientID is the federated client ID used for token exchange
|
||||
// idToken is the Identity token from the identity provider
|
||||
// tags is the list of tags to be associated with the auth key
|
||||
// audience is the federated audience acquired by configuring
|
||||
// the trusted credential in the admin UI
|
||||
var HookResolveAuthKeyViaWIF feature.Hook[func(ctx context.Context, baseURL, clientID, idToken, audience string, tags []string) (string, error)]
|
||||
|
||||
// HookExchangeJWTForTokenViaWIF resolves to [identityfederation.exchangeJWTForToken] when the
|
||||
// corresponding feature tag is enabled in the build process.
|
||||
//
|
||||
// baseURL is the URL of the control server used for token exchange
|
||||
// clientID is the federated client ID used for token exchange
|
||||
// idToken is the Identity token from the identity provider
|
||||
var HookExchangeJWTForTokenViaWIF feature.Hook[func(ctx context.Context, baseURL, clientID, idToken string) (string, error)]
|
||||
20
vendor/tailscale.com/internal/client/tailscale/oauthkeys.go
generated
vendored
Normal file
20
vendor/tailscale.com/internal/client/tailscale/oauthkeys.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package tailscale
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tailscale.com/feature"
|
||||
)
|
||||
|
||||
// HookResolveAuthKey resolves to [oauthkey.ResolveAuthKey] when the
|
||||
// corresponding feature tag is enabled in the build process.
|
||||
//
|
||||
// authKey is a standard device auth key or an OAuth client secret to
|
||||
// resolve into an auth key.
|
||||
// tags is the list of tags being advertised by the client (required to be
|
||||
// provided for the OAuth secret case, and required to be the same as the
|
||||
// list of tags for which the OAuth secret is allowed to issue auth keys).
|
||||
var HookResolveAuthKey feature.Hook[func(ctx context.Context, authKey string, tags []string) (string, error)]
|
||||
86
vendor/tailscale.com/internal/client/tailscale/tailscale.go
generated
vendored
Normal file
86
vendor/tailscale.com/internal/client/tailscale/tailscale.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package tailscale provides a minimal control plane API client for internal
|
||||
// use. A full client for 3rd party use is available at
|
||||
// tailscale.com/client/tailscale/v2. The internal client is provided to avoid
|
||||
// having to import that whole package.
|
||||
package tailscale
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
tsclient "tailscale.com/client/tailscale"
|
||||
)
|
||||
|
||||
// maxSize is the maximum read size (10MB) of responses from the server.
|
||||
const maxReadSize = 10 << 20
|
||||
|
||||
func init() {
|
||||
tsclient.I_Acknowledge_This_API_Is_Unstable = true
|
||||
}
|
||||
|
||||
// AuthMethod is an alias to tailscale.com/client/tailscale.
|
||||
type AuthMethod = tsclient.AuthMethod
|
||||
|
||||
// APIKey is an alias to tailscale.com/client/tailscale.
|
||||
type APIKey = tsclient.APIKey
|
||||
|
||||
// Device is an alias to tailscale.com/client/tailscale.
|
||||
type Device = tsclient.Device
|
||||
|
||||
// DeviceFieldsOpts is an alias to tailscale.com/client/tailscale.
|
||||
type DeviceFieldsOpts = tsclient.DeviceFieldsOpts
|
||||
|
||||
// Key is an alias to tailscale.com/client/tailscale.
|
||||
type Key = tsclient.Key
|
||||
|
||||
// KeyCapabilities is an alias to tailscale.com/client/tailscale.
|
||||
type KeyCapabilities = tsclient.KeyCapabilities
|
||||
|
||||
// KeyDeviceCapabilities is an alias to tailscale.com/client/tailscale.
|
||||
type KeyDeviceCapabilities = tsclient.KeyDeviceCapabilities
|
||||
|
||||
// KeyDeviceCreateCapabilities is an alias to tailscale.com/client/tailscale.
|
||||
type KeyDeviceCreateCapabilities = tsclient.KeyDeviceCreateCapabilities
|
||||
|
||||
// ErrResponse is an alias to tailscale.com/client/tailscale.
|
||||
type ErrResponse = tsclient.ErrResponse
|
||||
|
||||
// NewClient is an alias to tailscale.com/client/tailscale.
|
||||
func NewClient(tailnet string, auth AuthMethod) *Client {
|
||||
return &Client{
|
||||
Client: tsclient.NewClient(tailnet, auth),
|
||||
}
|
||||
}
|
||||
|
||||
// Client is a wrapper of tailscale.com/client/tailscale.
|
||||
type Client struct {
|
||||
*tsclient.Client
|
||||
}
|
||||
|
||||
// HandleErrorResponse is an alias to tailscale.com/client/tailscale.
|
||||
func HandleErrorResponse(b []byte, resp *http.Response) error {
|
||||
return tsclient.HandleErrorResponse(b, resp)
|
||||
}
|
||||
|
||||
// SendRequest add the authentication key to the request and sends it. It
|
||||
// receives the response and reads up to 10MB of it.
|
||||
func SendRequest(c *Client, req *http.Request) ([]byte, *http.Response, error) {
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read response. Limit the response to 10MB.
|
||||
// This limit is carried over from client/tailscale/tailscale.go.
|
||||
body := io.LimitReader(resp.Body, maxReadSize+1)
|
||||
b, err := io.ReadAll(body)
|
||||
if len(b) > maxReadSize {
|
||||
err = errors.New("API response too large")
|
||||
}
|
||||
return b, resp, err
|
||||
}
|
||||
133
vendor/tailscale.com/internal/client/tailscale/vip_service.go
generated
vendored
Normal file
133
vendor/tailscale.com/internal/client/tailscale/vip_service.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package tailscale
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/httpm"
|
||||
)
|
||||
|
||||
// VIPService is a Tailscale VIPService with Tailscale API JSON representation.
|
||||
type VIPService struct {
|
||||
// Name is a VIPService name in form svc:<leftmost-label-of-service-DNS-name>.
|
||||
Name tailcfg.ServiceName `json:"name,omitempty"`
|
||||
// Addrs are the IP addresses of the VIP Service. There are two addresses:
|
||||
// the first is IPv4 and the second is IPv6.
|
||||
// When creating a new VIP Service, the IP addresses are optional: if no
|
||||
// addresses are specified then they will be selected. If an IPv4 address is
|
||||
// specified at index 0, then that address will attempt to be used. An IPv6
|
||||
// address can not be specified upon creation.
|
||||
Addrs []string `json:"addrs,omitempty"`
|
||||
// Comment is an optional text string for display in the admin panel.
|
||||
Comment string `json:"comment,omitempty"`
|
||||
// Annotations are optional key-value pairs that can be used to store arbitrary metadata.
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
// Ports are the ports of a VIPService that will be configured via Tailscale serve config.
|
||||
// If set, any node wishing to advertise this VIPService must have this port configured via Tailscale serve.
|
||||
Ports []string `json:"ports,omitempty"`
|
||||
// Tags are optional ACL tags that will be applied to the VIPService.
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// VIPServiceList represents the JSON response to the list VIP Services API.
|
||||
type VIPServiceList struct {
|
||||
VIPServices []VIPService `json:"vipServices"`
|
||||
}
|
||||
|
||||
// GetVIPService retrieves a VIPService by its name. It returns 404 if the VIPService is not found.
|
||||
func (client *Client) GetVIPService(ctx context.Context, name tailcfg.ServiceName) (*VIPService, error) {
|
||||
path := client.BuildTailnetURL("vip-services", name.String())
|
||||
req, err := http.NewRequestWithContext(ctx, httpm.GET, path, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating new HTTP request: %w", err)
|
||||
}
|
||||
b, resp, err := SendRequest(client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making Tailsale API request: %w", err)
|
||||
}
|
||||
// If status code was not successful, return the error.
|
||||
// TODO: Change the check for the StatusCode to include other 2XX success codes.
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, HandleErrorResponse(b, resp)
|
||||
}
|
||||
svc := &VIPService{}
|
||||
if err := json.Unmarshal(b, svc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
// ListVIPServices retrieves all existing Services and returns them as a list.
|
||||
func (client *Client) ListVIPServices(ctx context.Context) (*VIPServiceList, error) {
|
||||
path := client.BuildTailnetURL("vip-services")
|
||||
req, err := http.NewRequestWithContext(ctx, httpm.GET, path, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating new HTTP request: %w", err)
|
||||
}
|
||||
b, resp, err := SendRequest(client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making Tailsale API request: %w", err)
|
||||
}
|
||||
// If status code was not successful, return the error.
|
||||
// TODO: Change the check for the StatusCode to include other 2XX success codes.
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, HandleErrorResponse(b, resp)
|
||||
}
|
||||
result := &VIPServiceList{}
|
||||
if err := json.Unmarshal(b, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateVIPService creates or updates a VIPService by its name. Caller must ensure that, if the
|
||||
// VIPService already exists, the VIPService is fetched first to ensure that any auto-allocated IP addresses are not
|
||||
// lost during the update. If the VIPService was created without any IP addresses explicitly set (so that they were
|
||||
// auto-allocated by Tailscale) any subsequent request to this function that does not set any IP addresses will error.
|
||||
func (client *Client) CreateOrUpdateVIPService(ctx context.Context, svc *VIPService) error {
|
||||
data, err := json.Marshal(svc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := client.BuildTailnetURL("vip-services", svc.Name.String())
|
||||
req, err := http.NewRequestWithContext(ctx, httpm.PUT, path, bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating new HTTP request: %w", err)
|
||||
}
|
||||
b, resp, err := SendRequest(client, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making Tailscale API request: %w", err)
|
||||
}
|
||||
// If status code was not successful, return the error.
|
||||
// TODO: Change the check for the StatusCode to include other 2XX success codes.
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return HandleErrorResponse(b, resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteVIPService deletes a VIPService by its name. It returns an error if the VIPService
|
||||
// does not exist or if the deletion fails.
|
||||
func (client *Client) DeleteVIPService(ctx context.Context, name tailcfg.ServiceName) error {
|
||||
path := client.BuildTailnetURL("vip-services", name.String())
|
||||
req, err := http.NewRequestWithContext(ctx, httpm.DELETE, path, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating new HTTP request: %w", err)
|
||||
}
|
||||
b, resp, err := SendRequest(client, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making Tailscale API request: %w", err)
|
||||
}
|
||||
// If status code was not successful, return the error.
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return HandleErrorResponse(b, resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
187
vendor/tailscale.com/internal/noiseconn/conn.go
generated
vendored
187
vendor/tailscale.com/internal/noiseconn/conn.go
generated
vendored
@@ -1,187 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package noiseconn contains an internal-only wrapper around controlbase.Conn
|
||||
// that properly handles the early payload sent by the server before the HTTP/2
|
||||
// session begins.
|
||||
//
|
||||
// See the documentation on the Conn type for more details.
|
||||
package noiseconn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
// Conn is a wrapper around controlbase.Conn.
|
||||
//
|
||||
// It allows attaching an ID to a connection to allow cleaning up references in
|
||||
// the pool when the connection is closed, properly handles an optional "early
|
||||
// payload" that's sent prior to beginning the HTTP/2 session, and provides a
|
||||
// way to return a connection to a pool when the connection is closed.
|
||||
type Conn struct {
|
||||
*controlbase.Conn
|
||||
id int
|
||||
onClose func(int)
|
||||
h2cc *http2.ClientConn
|
||||
|
||||
readHeaderOnce sync.Once // guards init of reader field
|
||||
reader io.Reader // (effectively Conn.Reader after header)
|
||||
earlyPayloadReady chan struct{} // closed after earlyPayload is set (including set to nil)
|
||||
earlyPayload *tailcfg.EarlyNoise
|
||||
earlyPayloadErr error
|
||||
}
|
||||
|
||||
// New creates a new Conn that wraps the given controlbase.Conn.
|
||||
//
|
||||
// h2t is the HTTP/2 transport to use for the connection; a new
|
||||
// http2.ClientConn will be created that reads from the returned Conn.
|
||||
//
|
||||
// connID should be a unique ID for this connection. When the Conn is closed,
|
||||
// the onClose function will be called with the connID if it is non-nil.
|
||||
func New(conn *controlbase.Conn, h2t *http2.Transport, connID int, onClose func(int)) (*Conn, error) {
|
||||
ncc := &Conn{
|
||||
Conn: conn,
|
||||
id: connID,
|
||||
onClose: onClose,
|
||||
earlyPayloadReady: make(chan struct{}),
|
||||
}
|
||||
h2cc, err := h2t.NewClientConn(ncc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ncc.h2cc = h2cc
|
||||
return ncc, nil
|
||||
}
|
||||
|
||||
// RoundTrip implements the http.RoundTripper interface.
|
||||
func (c *Conn) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return c.h2cc.RoundTrip(r)
|
||||
}
|
||||
|
||||
// GetEarlyPayload waits for the early Noise payload to arrive.
|
||||
// It may return (nil, nil) if the server begins HTTP/2 without one.
|
||||
//
|
||||
// It is safe to call this multiple times; all callers will block until the
|
||||
// early Noise payload is ready (if any) and will return the same result for
|
||||
// the lifetime of the Conn.
|
||||
func (c *Conn) GetEarlyPayload(ctx context.Context) (*tailcfg.EarlyNoise, error) {
|
||||
select {
|
||||
case <-c.earlyPayloadReady:
|
||||
return c.earlyPayload, c.earlyPayloadErr
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// ReserveNewRequest will reserve a new concurrent request on the connection.
|
||||
//
|
||||
// It returns whether the reservation was successful, and any early Noise
|
||||
// payload if present. If a reservation was not successful, it will return
|
||||
// false and nil for the early payload.
|
||||
func (c *Conn) ReserveNewRequest(ctx context.Context) (bool, *tailcfg.EarlyNoise, error) {
|
||||
earlyPayloadMaybeNil, err := c.GetEarlyPayload(ctx)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if c.h2cc.ReserveNewRequest() {
|
||||
return true, earlyPayloadMaybeNil, nil
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
// CanTakeNewRequest reports whether the underlying HTTP/2 connection can take
|
||||
// a new request, meaning it has not been closed or received or sent a GOAWAY.
|
||||
func (c *Conn) CanTakeNewRequest() bool {
|
||||
return c.h2cc.CanTakeNewRequest()
|
||||
}
|
||||
|
||||
// The first 9 bytes from the server to client over Noise are either an HTTP/2
|
||||
// settings frame (a normal HTTP/2 setup) or, as we added later, an "early payload"
|
||||
// header that's also 9 bytes long: 5 bytes (EarlyPayloadMagic) followed by 4 bytes
|
||||
// of length. Then that many bytes of JSON-encoded tailcfg.EarlyNoise.
|
||||
// The early payload is optional. Some servers may not send it.
|
||||
const (
|
||||
hdrLen = 9 // http2 frame header size; also size of our early payload size header
|
||||
)
|
||||
|
||||
// EarlyPayloadMagic is the 5-byte magic prefix that indicates an early payload.
|
||||
const EarlyPayloadMagic = "\xff\xff\xffTS"
|
||||
|
||||
// returnErrReader is an io.Reader that always returns an error.
|
||||
type returnErrReader struct {
|
||||
err error // the error to return
|
||||
}
|
||||
|
||||
func (r returnErrReader) Read([]byte) (int, error) { return 0, r.err }
|
||||
|
||||
// Read is basically the same as controlbase.Conn.Read, but it first reads the
|
||||
// "early payload" header from the server which may or may not be present,
|
||||
// depending on the server.
|
||||
func (c *Conn) Read(p []byte) (n int, err error) {
|
||||
c.readHeaderOnce.Do(c.readHeader)
|
||||
return c.reader.Read(p)
|
||||
}
|
||||
|
||||
// readHeader reads the optional "early payload" from the server that arrives
|
||||
// after the Noise handshake but before the HTTP/2 session begins.
|
||||
//
|
||||
// readHeader is responsible for reading the header (if present), initializing
|
||||
// c.earlyPayload, closing c.earlyPayloadReady, and initializing c.reader for
|
||||
// future reads.
|
||||
func (c *Conn) readHeader() {
|
||||
defer close(c.earlyPayloadReady)
|
||||
|
||||
setErr := func(err error) {
|
||||
c.reader = returnErrReader{err}
|
||||
c.earlyPayloadErr = err
|
||||
}
|
||||
|
||||
var hdr [hdrLen]byte
|
||||
if _, err := io.ReadFull(c.Conn, hdr[:]); err != nil {
|
||||
setErr(err)
|
||||
return
|
||||
}
|
||||
if string(hdr[:len(EarlyPayloadMagic)]) != EarlyPayloadMagic {
|
||||
// No early payload. We have to return the 9 bytes read we already
|
||||
// consumed.
|
||||
c.reader = io.MultiReader(bytes.NewReader(hdr[:]), c.Conn)
|
||||
return
|
||||
}
|
||||
epLen := binary.BigEndian.Uint32(hdr[len(EarlyPayloadMagic):])
|
||||
if epLen > 10<<20 {
|
||||
setErr(errors.New("invalid early payload length"))
|
||||
return
|
||||
}
|
||||
payBuf := make([]byte, epLen)
|
||||
if _, err := io.ReadFull(c.Conn, payBuf); err != nil {
|
||||
setErr(err)
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(payBuf, &c.earlyPayload); err != nil {
|
||||
setErr(err)
|
||||
return
|
||||
}
|
||||
c.reader = c.Conn
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (c *Conn) Close() error {
|
||||
if err := c.Conn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.onClose != nil {
|
||||
c.onClose(c.id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user