kubeshare

package
v0.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package kubeshare is a Kubernetes cluster shared with a cloop hub through a least-privilege ServiceAccount (Task 20416).

Why it exists

The clusters enterprises run authenticate people through exec credential plugins — kubelogin on AKS, `aws eks get-token` on EKS, the GKE plugin — and the hub refuses such a kubeconfig, because running the plugin there would execute a program named in an uploaded file on the control-plane host. The refusal used to be the end of it: the developer had to create a ServiceAccount, choose and bind roles, mint a token and assemble a kubeconfig by hand, and the token then expired mid-run because managed API servers cap how long one may live.

A share does that work from the developer's own terminal (`cloop remote kube share`, which runs the plugin there through pkg/kubeexec) and leaves the hub holding only a static kubeconfig for a ServiceAccount that can do exactly what the developer chose:

  • a labelled ServiceAccount in a namespace the developer names;
  • a RoleBinding in each shared namespace to the cluster's own `view` role, or `edit` for write access — never a ClusterRoleBinding unless asked;
  • a bounded token, minted through the TokenRequest API.

When the hub runs the Kubernetes access monitor, the share renews itself: one more Role lets the ServiceAccount create a token for itself and nobody else (resourceNames), and the hub uses it to mint the next token before the current one expires. The monitor refuses the serviceaccounts/token subresource to every sandbox (pkg/kubeguard), so the permission is the hub's to spend and never the workload's. Without the monitor the token would sit in the sandbox, so no such Role is installed and the developer renews with `cloop remote kube renew`.

What this package is, and is not

The plan of objects, the few Kubernetes API calls a share makes, the token's claims, the static kubeconfig, the metadata the hub keeps on the secret, and the hub's renewal. It never runs a credential plugin and never reads a file a kubeconfig names: the hub imports it, and tests/security holds it to that.

Index

Constants

View Source
const (
	// MinTTL is the shortest token the TokenRequest API issues.
	MinTTL = 10 * time.Minute
	// MaxTTL is the longest token a share asks for. A share exists so that
	// nobody hands a sandbox a credential that outlives the work; a token for
	// longer than a week is that credential.
	MaxTTL = 7 * 24 * time.Hour
	// DefaultTTL is what a share asks for when --ttl is not given.
	DefaultTTL = 24 * time.Hour
)

Token lifetimes.

View Source
const (
	// MetaShare is the share's id, and the mark that a kubeconfig secret is a
	// share at all.
	MetaShare = "kube.share"
	// MetaRenewal is RenewalHub or RenewalManual.
	MetaRenewal = secretbroker.KubeShareMetaRenewal
	// MetaServiceAccount is "<namespace>/<name>".
	MetaServiceAccount = "kube.service_account"
	// MetaNamespaces lists the shared namespaces, comma-separated; "*" for a
	// share bound cluster-wide.
	MetaNamespaces = "kube.namespaces"
	// MetaAccess is read or write.
	MetaAccess = "kube.access"
	// MetaServer is the API server the share's kubeconfig names.
	MetaServer = "kube.server"
	// MetaContext is the developer's kubeconfig context the share was made
	// from, which renew and revoke default to.
	MetaContext = "kube.context"
	// MetaObjects lists the objects the share created, for revoke.
	MetaObjects = "kube.objects"
	// MetaIssuedAt and MetaExpiresAt bound the token the secret holds.
	MetaIssuedAt  = "kube.issued_at"
	MetaExpiresAt = "kube.expires_at"
	// MetaRenewedAt, MetaRenewedBy and MetaRenewals describe the last renewal.
	MetaRenewedAt = "kube.renewed_at"
	MetaRenewedBy = "kube.renewed_by"
	MetaRenewals  = "kube.renewals"
	// MetaRenewError and MetaRenewErrorAt hold why the last renewal attempt
	// failed, until one succeeds.
	MetaRenewError   = "kube.renew_error"
	MetaRenewErrorAt = "kube.renew_error_at"
	// MetaRenewState is "lapsed" once the token expired with no renewal: the
	// hub cannot renew it any more, only its owner can — or "stopped" while
	// the hub refuses to renew it because its owner is denied on the hub.
	MetaRenewState = "kube.renew_state"
	// MetaOwner is who made the share: the hub identity that uploaded it, as
	// the hub's owner key (a lowercased email, or "sub:" and the subject).
	// Written by the hub, never taken from the client (Task 20420): revoke
	// and renew act only on cluster objects whose owner annotation names it.
	MetaOwner = "kube.owner"
	// MetaCA is the SHA-256 of the cluster CA the share's kubeconfig trusts,
	// hex, or CANone when it carries none. Written by the hub from the
	// kubeconfig itself: renew and revoke refuse a local context that trusts
	// another.
	MetaCA = "kube.ca_sha256"
)

The metadata keys the hub keeps on a share's secret. None is a credential — metadata lands in audit rows and API responses — and the time fields are derived by the hub from the token itself, not trusted from the client.

View Source
const (
	RenewStateLapsed  = "lapsed"
	RenewStateStopped = "stopped"
)

MetaRenewState's values.

View Source
const (
	LabelShare     = "cloop.dev/kube-share"
	LabelManagedBy = "app.kubernetes.io/managed-by"

	AnnotationName    = "cloop.dev/share-name"
	AnnotationHub     = "cloop.dev/hub"
	AnnotationOwner   = "cloop.dev/owner"
	AnnotationAccess  = "cloop.dev/access"
	AnnotationCreated = "cloop.dev/created-at"
)

Labels and annotations on every object a share creates. The label is what revoke and an administrator find them by; the selector deliberately shares nothing with the executor's own (cloop.dev/managed), so the Kubernetes executor's orphan sweep can never mistake a share for something it owns.

View Source
const CANone = "none"

CANone is MetaCA for a kubeconfig that names no CA of its own and trusts the system's.

Variables

View Source
var (
	// ErrLapsed is a token that expired before it was renewed. Nothing the
	// hub holds can authenticate a renewal any more; only the share's owner,
	// with their own credentials, can mint the next token.
	ErrLapsed = errors.New("the share's token expired before it was renewed")
	// ErrUnrenewable is a stored kubeconfig a share could not have written:
	// it parses to no single ServiceAccount token.
	ErrUnrenewable = errors.New("the stored kubeconfig is not a renewable share")
	// ErrWrongToken is a pushed token that is not the share's ServiceAccount's.
	ErrWrongToken = errors.New("the token is not for the share's ServiceAccount")
	// ErrWrongServer is a pushed token minted at another API server than the
	// share's kubeconfig names, or through another CA — or one whose origin
	// nobody named (Task 20420). The hub never sends a token anywhere but the
	// server that minted it.
	ErrWrongServer = errors.New("the token was not minted by the share's API server")
)

Errors a renewal distinguishes.

View Source
var ErrNotServiceAccountToken = errors.New("not a bounded ServiceAccount token")

ErrNotServiceAccountToken is a token that is not a bounded ServiceAccount token: not a JWT, not for a ServiceAccount, or without an expiry.

Functions

func CAFingerprint

func CAFingerprint(caData []byte) string

CAFingerprint is MetaCA for a kubeconfig carrying caData.

func ClearStopped

func ClearStopped(meta map[string]string)

ClearStopped takes RecordStopped back, for an owner no longer denied.

func CollectionPath

func CollectionPath(o Object) string

CollectionPath is where an object of o's kind is created.

func DeleteCommand

func DeleteCommand(shareID, ns string) string

DeleteCommand is the kubectl invocation that removes whatever carries the share's label in namespace ns. For the share's own terminal, cleaning up after a share it failed to finish; a revoke advises FindCommand instead, since anything labelled with the id would match.

func DeleteCommandFor

func DeleteCommandFor(objs []Object) string

DeleteCommandFor is the kubectl invocation that removes exactly objs.

func DescribePlugin

func DescribePlugin(command string, args []string) string

DescribePlugin names an exec plugin the way its users know it: the program, and the platform it signs in to when that is recognisable.

func Expiry

func Expiry(expires, now time.Time) string

Expiry describes a token's expiry relative to now, for a person.

func FailureText

func FailureText(reason string) string

FailureText is a renewal failure as RecordFailure stores it: trimmed and bounded, so a caller can tell whether the stored one already says it.

func FindCommand

func FindCommand(shareID, ns string) string

FindCommand is the kubectl invocation that lists whatever carries the share's label in namespace ns, with the owner annotation to check each against before deleting it.

func FormatObjects

func FormatObjects(objs []Object) string

FormatObjects renders objects for MetaObjects: "Kind:ns/name" entries, comma separated, "Kind:name" for a cluster-scoped one. Names and namespaces are DNS labels, so none of the separators can occur inside one.

func IsConflict

func IsConflict(err error) bool

func IsForbidden

func IsForbidden(err error) bool

func IsNotFound

func IsNotFound(err error) bool

IsNotFound, IsConflict, IsForbidden and IsUnauthorized classify an APIError.

func IsShare

func IsShare(meta map[string]string) bool

IsShare reports whether metadata marks a kubeconfig secret as a share.

func IsShareServiceAccount

func IsShareServiceAccount(name, id string) bool

IsShareServiceAccount reports whether name is the ServiceAccount a share with this id creates: "cloop-<name>-<id>", as serviceAccountName builds it. The hub holds a share to it (Task 20420), so a share's token — whose claims nobody verified — cannot point renew at some other ServiceAccount.

func IsUnauthorized

func IsUnauthorized(err error) bool

func Lapsed

func Lapsed(err error) bool

Lapsed reports whether err means the hub can never renew the share again on its own: the token expired, or the cluster no longer accepts it — the ServiceAccount was deleted, or the token revoked with it.

func Mint

func Mint(ctx context.Context, c *Client, p *Plan) (Token, Claims, error)

Mint mints the share's first token for the plan's ServiceAccount.

func NewID

func NewID() (string, error)

NewID returns a fresh share id.

func ObjectPath

func ObjectPath(o Object) string

ObjectPath is o's own path.

func OwnerMatches

func OwnerMatches(annotation, owner string) bool

OwnerMatches reports whether an object's owner annotation names owner, the hub's record of who made the share (MetaOwner).

The annotation is written by the terminal that made the share: since Task 20420 the hub's own owner key, from /api/me; before that the email as the identity provider spelled it, or the bare subject. All three compare equal to the owner key they stand for. A share with no recorded owner — made on a hub with no identity provider — matches only an object carrying none.

func RecordFailure

func RecordFailure(meta map[string]string, reason string, at time.Time, lapsed bool)

RecordFailure stamps a failed renewal attempt. lapsed marks a token that has expired, which only its owner can now replace.

func RecordRenewal

func RecordRenewal(meta map[string]string, c Claims, by string, at time.Time)

RecordRenewal stamps a successful renewal: new times, who renewed, one more renewal, and any earlier failure cleared.

func RecordStopped

func RecordStopped(meta map[string]string, reason string, at time.Time)

RecordStopped stamps a share the hub stopped renewing, and why: its owner is denied on the hub (Task 20420). A lapsed share stays lapsed.

func RecordToken

func RecordToken(meta map[string]string, c Claims)

RecordToken writes the token's own claims into meta: the hub's half of the metadata, from the credential rather than from whoever sent it.

func RenderKubeconfig

func RenderKubeconfig(p KubeconfigParams) ([]byte, error)

RenderKubeconfig builds the static kubeconfig a share stores on the hub.

func ReplaceToken

func ReplaceToken(kubeconfig []byte, token string) ([]byte, error)

ReplaceToken returns kubeconfig with the current context's user's token set to token, and nothing else changed — a renewal changes the credential, never the cluster it is for.

func ShareCommand

func ShareCommand(name, context, namespace, hub string) string

ShareCommand is the command that shares context on hub as secret name. namespace is the context's default namespace, or empty for a placeholder.

func ShortDuration

func ShortDuration(d time.Duration) string

ShortDuration is shortDuration for callers rendering a share.

func UploadRefusal

func UploadRefusal(raw []byte, name, hub string) string

UploadRefusal returns why an uploaded kubeconfig cannot be stored, or "" when it can. name is the secret name the uploader chose and hub the hub's URL, so the command in the message is the one to run.

func ValidNamespace

func ValidNamespace(s string) bool

ValidNamespace reports whether s can name a namespace.

func ValidServiceAccountName

func ValidServiceAccountName(s string) bool

ValidServiceAccountName reports whether s can name a ServiceAccount — and so one of a share's objects, which are all named after it.

func ValidShareID

func ValidShareID(s string) bool

ValidShareID reports whether s can be a share's id.

func VerifyServiceAccount

func VerifyServiceAccount(ctx context.Context, c *Client, s Share) error

VerifyServiceAccount checks with the cluster that the share's ServiceAccount is one the share created: named as a share names it, and read back carrying the share's label and an owner annotation naming its owner (Task 20420). Renew mints a token for it with the developer's own credentials, so this is what stops a share's metadata pointing that at somebody else's ServiceAccount — kube-system's, say — and the hub then holding a token for it.

Types

type APIError

type APIError struct {
	Method  string
	Path    string
	Status  int
	Reason  string
	Message string
}

APIError is a refusal from the API server, decoded from its Status object.

func (*APIError) Error

func (e *APIError) Error() string

type Access

type Access string

Access is what a share lets its ServiceAccount do in the shared namespaces.

const (
	// AccessRead binds the cluster's `view` role: read most objects, not
	// Secrets, and nothing that writes.
	AccessRead Access = "read"
	// AccessWrite binds `edit`: read and write most namespaced objects.
	AccessWrite Access = "write"
)

func ParseAccess

func ParseAccess(s string) (Access, error)

ParseAccess reads --access.

func (Access) ClusterRole

func (a Access) ClusterRole() string

ClusterRole is the built-in role a binding of this access names.

type Claims

type Claims struct {
	Namespace      string
	ServiceAccount string
	UID            string
	IssuedAt       time.Time
	ExpiresAt      time.Time
}

Claims is what a ServiceAccount token says about itself.

func Inspect

func Inspect(kubeconfig []byte) (*kubernetes.RESTConfig, Claims, error)

Inspect parses a stored share kubeconfig and returns its REST config and its token's claims.

func ParseClaims

func ParseClaims(token string) (Claims, error)

ParseClaims reads a token's claims. It refuses anything a share could not have minted: a share's token is always a JWT for a ServiceAccount, with an issue time and an expiry.

func (Claims) Lifetime

func (c Claims) Lifetime() time.Duration

Lifetime is how long the token was issued for.

func (Claims) Subject

func (c Claims) Subject() string

Subject is the username the API server authenticates the token as.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client talks to one API server as one credential.

func NewClient

func NewClient(rc *kubernetes.RESTConfig) (*Client, error)

NewClient builds a client for rc. It performs no I/O.

func (*Client) Close

func (c *Client) Close()

Close releases pooled connections.

func (*Client) Create

func (c *Client) Create(ctx context.Context, m Manifest) error

Create creates one object.

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, o Object) error

Delete deletes one object. Gone already is success: revoke is idempotent.

It deletes whatever the name holds. Only a share's own rollback, deleting what it created a moment ago, may use it; revoke deletes through DeleteExact, which names the object it read.

func (*Client) DeleteExact

func (c *Client) DeleteExact(ctx context.Context, o Object, meta ObjectMeta) error

DeleteExact deletes o only if it is still the object read as meta: the API server checks the uid and resourceVersion preconditions and refuses (409) if either changed — so an object swapped, relabelled or re-annotated after it was checked is not the one deleted. Gone already is success.

func (*Client) Get

func (c *Client) Get(ctx context.Context, o Object) (ObjectMeta, error)

Get reads an object's metadata. An object that is not one a share could have created is refused before any request is built from its name.

func (*Client) ListLabelled

func (c *Client) ListLabelled(ctx context.Context, kind, namespace, shareID string) ([]Labelled, error)

ListLabelled lists the objects of a kind in namespace (or cluster-wide for a ClusterRoleBinding) that carry the share's label, with their metadata.

func (*Client) RequestToken

func (c *Client) RequestToken(ctx context.Context, namespace, serviceAccount string, ttl time.Duration) (Token, error)

RequestToken mints a token for namespace/serviceAccount through the TokenRequest API, valid for ttl or whatever shorter time the cluster caps it at — managed API servers do cap it, which is the reason renewal exists.

func (*Client) Server

func (c *Client) Server() string

Server is the API server the client talks to.

func (*Client) WhoAmI

func (c *Client) WhoAmI(ctx context.Context) (username string, ok bool, err error)

WhoAmI asks the API server who this client's credential is (SelfSubjectReview). ok is false on a cluster too old to answer (before 1.27 without the beta enabled), which callers treat as "cannot tell", never as "nobody".

type KubeconfigParams

type KubeconfigParams struct {
	// Name is the context's, the cluster's and the user's name: the share's.
	Name string
	// Server, CAData, TLSServerName, Insecure and ProxyURL describe the
	// cluster exactly as the developer's kubeconfig did.
	Server        string
	CAData        []byte
	TLSServerName string
	Insecure      bool
	ProxyURL      string
	// Namespace is the context's default namespace.
	Namespace string
	// Token is the ServiceAccount token.
	Token string
}

KubeconfigParams describe the static kubeconfig a share stores.

type Labelled

type Labelled struct {
	Object
	Meta ObjectMeta
}

Labelled is an object a labelled listing found, with its metadata as listed.

type Manifest

type Manifest struct {
	Object
	// Collection is the API path the object is created under.
	Collection string
	// Body is the object.
	Body map[string]any
}

Manifest is one object to create: where to POST it and what to send.

type Object

type Object struct {
	Kind      string // ServiceAccount, RoleBinding, Role, ClusterRoleBinding
	Namespace string // empty for a ClusterRoleBinding
	Name      string
}

Object is one Kubernetes object a share created.

func Apply

func Apply(ctx context.Context, c *Client, p *Plan) ([]Object, error)

Apply creates the plan's objects in order. If one fails it deletes the ones it created, newest first, and returns the failure together with anything the rollback could not remove — a half-made share is worse than none, because a binding left behind grants access nobody is tracking.

An object that already exists is a failure too, never adopted: its name carries this share's id, so it can only be left over from something else.

func ParseObjects

func ParseObjects(s string) []Object

ParseObjects reads MetaObjects back. Malformed entries are skipped: revoke also finds objects by label, so a lost entry costs nothing.

func Remaining

func Remaining(ctx context.Context, c *Client, s Share) ([]Object, error)

Remaining checks, after a removal, that nothing the share created is left in the namespaces it names — nothing carrying its label and its owner — and returns what is. An object carrying the label for another owner is not the share's to remove, and Remove reported it as refused.

func (Object) String

func (o Object) String() string

func (Object) Valid

func (o Object) Valid() bool

Valid reports whether o is an object a share could have created: one of its four kinds, named as a share names things, in a namespace exactly when its kind lives in one. Every path revoke builds from an object goes through this, so a name carrying "/" or ".." can never steer a request elsewhere.

type ObjectMeta

type ObjectMeta struct {
	Name            string            `json:"name"`
	Namespace       string            `json:"namespace"`
	UID             string            `json:"uid"`
	ResourceVersion string            `json:"resourceVersion"`
	Labels          map[string]string `json:"labels"`
	Annotations     map[string]string `json:"annotations"`
}

ObjectMeta is the part of an object a share reads back.

type Origin

type Origin struct {
	Server        string
	CAFingerprint string
}

Origin is where a token pushed to the hub was minted: the API server the terminal's context reached and the CA it trusted there.

type Plan

type Plan struct {
	Request
	// ServiceAccount is the ServiceAccount's name.
	ServiceAccount string
	// contains filtered or unexported fields
}

Plan is a validated request with every name decided.

func NewPlan

func NewPlan(req Request, now time.Time) (*Plan, error)

NewPlan validates req and decides every name.

func (*Plan) Describe

func (p *Plan) Describe() []string

Describe renders the plan for a person, one line per decision.

func (*Plan) Manifests

func (p *Plan) Manifests() []Manifest

Manifests renders every object in creation order.

func (*Plan) Objects

func (p *Plan) Objects() []Object

Objects lists what the plan creates, in creation order: the ServiceAccount first, so every binding names something that exists.

func (*Plan) RenewRoleName

func (p *Plan) RenewRoleName() string

RenewRoleName is the self-renewal Role's name, and its binding's.

func (*Plan) Share

func (p *Plan) Share() Share

Share is the plan as the hub's metadata will describe it.

type PluginUse

type PluginUse struct {
	User string
	// Contexts are the contexts that use the user, sorted.
	Contexts []string
	// Namespace is the first such context's default namespace, if it has one.
	Namespace string
	// Command and Args are the exec plugin's; AuthProvider the auth-provider's
	// name when that is what the user has instead.
	Command      string
	Args         []string
	AuthProvider string
}

PluginUse is one kubeconfig user that signs in through a credential plugin.

func PluginUses

func PluginUses(raw []byte) ([]PluginUse, error)

PluginUses lists the users of raw that sign in through a credential plugin and are used by at least one context. A user no context names is not reported: it reaches nothing.

func (PluginUse) Plugin

func (u PluginUse) Plugin() string

Plugin names the plugin for a person.

type Removal

type Removal struct {
	// Removed are the objects deleted.
	Removed []Object
	// Gone are the objects Remove looked for and the cluster no longer has.
	Gone []Object
	// Refused are objects Remove found and did not delete, because nothing
	// read back from the cluster says this share created them, with why.
	Refused map[Object]string
	// Failed are the ones that could not be removed, with why — including a
	// ServiceAccount Remove refused, which leaves every token minted for it
	// valid until it expires.
	Failed map[Object]error
}

Removal is what Remove did.

func Remove

func Remove(ctx context.Context, c *Client, s Share) Removal

Remove deletes what a share created in the cluster, and nothing else.

Revoke runs with the developer's own credentials — often an administrator's — over a share whose metadata its uploader wrote. So nothing in that metadata is taken on trust (Task 20420). The candidates are what the share's plan creates (ExpectedObjects), what its metadata lists, and what carries its label in the namespaces it names; each is read back from the cluster and deleted only if it carries the share's label and an owner annotation naming the share's owner, and then by uid and resourceVersion, so an object that changed after it was read is not the one deleted. Anything else is left alone and reported in Refused.

The ServiceAccount goes first. Deleting it invalidates every token ever minted for it at once — the hub's, and any copy a workload kept — so the credential is dead before the first binding is touched, and a revoke that fails halfway leaves bindings to nothing rather than a live token. A ServiceAccount that exists and is not the share's is a failure, not just a refusal: whatever tokens the share holds stay valid until they expire.

func (Removal) Clean

func (r Removal) Clean() bool

Clean reports that nothing the share created was left behind.

type Renewal

type Renewal string

Renewal says who keeps a share's token current.

const (
	// RenewalHub: the hub mints the next token before the current one
	// expires, with the ServiceAccount's own self-renewal Role. Only offered
	// by a hub that runs the Kubernetes access monitor.
	RenewalHub Renewal = secretbroker.KubeShareRenewalHub
	// RenewalManual: the developer renews from their terminal with
	// `cloop remote kube renew`.
	RenewalManual Renewal = "manual"
)

type Renewed

type Renewed struct {
	Kubeconfig []byte
	// Claims are the new token's; Previous the replaced one's.
	Claims   Claims
	Previous Claims
	// Verified reports that the cluster itself confirmed who the new token
	// is (Adopt on a cluster that answers SelfSubjectReview).
	Verified bool
}

Renewed is a share's kubeconfig with a new token in it.

func Adopt

func Adopt(ctx context.Context, kubeconfig []byte, token string, origin Origin, now time.Time) (*Renewed, error)

Adopt checks a token the share's owner minted and returns the stored kubeconfig carrying it. The token must have been minted at the API server the stored kubeconfig names, through the CA it trusts (origin), be a bounded token for the share's own ServiceAccount, still valid, and — where the cluster can say — authenticated by the cluster the share names as that ServiceAccount.

The origin check comes first because the confirmation sends the token to the stored server. That server is the share's uploader's to name, and the token was minted with somebody else's credentials, so a token is never sent to a server other than the one that minted it (Task 20420).

func Renew

func Renew(ctx context.Context, kubeconfig []byte, now time.Time) (*Renewed, error)

Renew mints the next token for the share's ServiceAccount, authenticated by the token the share holds, for the lifetime that token was issued with.

type Request

type Request struct {
	// Name is the secret's name on the hub, and part of every object's name.
	Name string
	// ID is the share's id; empty generates one.
	ID string
	// Namespaces are where the ServiceAccount is bound.
	Namespaces []string
	// ServiceAccountNamespace is where the ServiceAccount lives. Empty means
	// the first shared namespace, for read access only.
	ServiceAccountNamespace string
	Access                  Access
	// ClusterWide binds `view` with a ClusterRoleBinding instead of one
	// RoleBinding per namespace. Read access only.
	ClusterWide bool
	TTL         time.Duration
	Renewal     Renewal
	// Hub and Owner annotate the objects, so whoever finds them in the
	// cluster can tell where they came from and whose they are.
	Hub   string
	Owner string
	// Context is the developer's kubeconfig context, recorded for renew and
	// revoke.
	Context string
	// Server is the API server the stored kubeconfig names.
	Server string
}

Request is what `cloop remote kube share` asks for.

type Share

type Share struct {
	ID             string
	Renewal        Renewal
	Namespace      string // the ServiceAccount's
	ServiceAccount string
	Namespaces     []string
	ClusterWide    bool
	Access         Access
	Server         string
	Context        string
	Objects        []Object
	IssuedAt       time.Time
	ExpiresAt      time.Time
	RenewedAt      time.Time
	RenewedBy      string
	Renewals       int
	RenewError     string
	RenewErrorAt   time.Time
	Lapsed         bool
	// Stopped is a share the hub will not renew while its owner is denied
	// on the hub — suspended, or offboarded with its secret kept under a
	// legal hold (Task 20420). RenewError says why.
	Stopped bool
	// Owner is MetaOwner: who made the share, as the hub recorded it.
	Owner string
	// CAFingerprint is MetaCA; empty for a share uploaded before the hub
	// recorded it, which renew and revoke cannot check a context against.
	CAFingerprint string
}

Share is a share as the hub's secret metadata describes it.

func FromMetadata

func FromMetadata(meta map[string]string) (Share, bool)

FromMetadata reads a share back. ok is false for a secret that is not one.

func (Share) Due

func (s Share) Due(now time.Time) bool

Due reports whether a share's token should be renewed now: less than half its lifetime remains. Half rather than a fixed margin, so a hub that is down for less than half a token's life never loses the chain — a 24-hour token is renewed every 12 hours and survives a 12-hour outage, a 10-minute one is renewed every 5 minutes.

A share whose times are unknown is due, which makes the renewal read them from the token and record them.

func (Share) ExpectedObjects

func (s Share) ExpectedObjects() []Object

ExpectedObjects lists what a share's plan creates, derived from its ServiceAccount, namespaces and renewal rather than read from MetaObjects — the list the hub records for a share it has checked (Task 20420), and the first thing revoke looks for.

func (Share) Expired

func (s Share) Expired(now time.Time) bool

Expired reports whether the token the hub holds has run out.

func (Share) Lifetime

func (s Share) Lifetime() time.Duration

Lifetime is the token's issued lifetime, which a renewal asks for again.

func (Share) Metadata

func (s Share) Metadata() map[string]string

Metadata renders a share's descriptive fields for the secret. The time and renewal-history fields are the hub's to write and are left to it.

func (Share) SameCluster

func (s Share) SameCluster(server string, caData []byte) error

SameCluster refuses a context that does not reach the share's cluster: its API server differs, or it trusts another CA (Task 20420). Renew and revoke act with the developer's own credentials, and the share's metadata — which names the context they default to — is its uploader's; acting on another cluster, or on the right URL through a TLS chain the share does not trust, is what this stops.

func (Share) SharedNamespaces

func (s Share) SharedNamespaces() string

SharedNamespaces renders the namespaces for a person: "team-a, team-b", or "every namespace" for a cluster-wide share.

type Token

type Token struct {
	Value     string
	ExpiresAt time.Time
}

Token is a minted ServiceAccount token.

func (Token) GoString

func (t Token) GoString() string

GoString mirrors String.

func (Token) String

func (t Token) String() string

String redacts the token.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL