Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/v1alpha1/ntncellconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ type RemoteControlRef struct {
// 1-63) — a permanent admission error beats a silent tight-requeue on a mistyped
// value. The pattern alone cannot bound the label/host length (a regex quantifier
// would, but the DNS-1123 label form makes that unreadable), so CEL carries it.
//
// Shape validation does NOT restrict WHICH host the operator will dial; the endpoint is
// CR-author-controlled, so confining it is an admin egress control (SSRF), not a CRD rule.
// Set the operator flag --remote-control-allowed-endpoint-hosts to permit only sanctioned
// gNB hosts for the runtime push (empty = any, backward compatible), and pair it with the
// operator egress NetworkPolicy (config/network-policy/allow-egress-traffic.yaml). See #299.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=261
// +kubebuilder:validation:Pattern=`^(\[[0-9a-fA-F:]+\]|[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?)*):[0-9]{1,5}$`
Expand Down
6 changes: 6 additions & 0 deletions bundle/manifests/ntn.operators.dev_ntncellconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,12 @@ spec:
1-63) — a permanent admission error beats a silent tight-requeue on a mistyped
value. The pattern alone cannot bound the label/host length (a regex quantifier
would, but the DNS-1123 label form makes that unreadable), so CEL carries it.

Shape validation does NOT restrict WHICH host the operator will dial; the endpoint is
CR-author-controlled, so confining it is an admin egress control (SSRF), not a CRD rule.
Set the operator flag --remote-control-allowed-endpoint-hosts to permit only sanctioned
gNB hosts for the runtime push (empty = any, backward compatible), and pair it with the
operator egress NetworkPolicy (config/network-policy/allow-egress-traffic.yaml). See #299.
maxLength: 261
minLength: 1
pattern: ^(\[[0-9a-fA-F:]+\]|[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?)*):[0-9]{1,5}$
Expand Down
6 changes: 6 additions & 0 deletions config/crd/bases/ntn.operators.dev_ntncellconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,12 @@ spec:
1-63) — a permanent admission error beats a silent tight-requeue on a mistyped
value. The pattern alone cannot bound the label/host length (a regex quantifier
would, but the DNS-1123 label form makes that unreadable), so CEL carries it.

Shape validation does NOT restrict WHICH host the operator will dial; the endpoint is
CR-author-controlled, so confining it is an admin egress control (SSRF), not a CRD rule.
Set the operator flag --remote-control-allowed-endpoint-hosts to permit only sanctioned
gNB hosts for the runtime push (empty = any, backward compatible), and pair it with the
operator egress NetworkPolicy (config/network-policy/allow-egress-traffic.yaml). See #299.
maxLength: 261
minLength: 1
pattern: ^(\[[0-9a-fA-F:]+\]|[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?)*):[0-9]{1,5}$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,12 @@ spec:
1-63) — a permanent admission error beats a silent tight-requeue on a mistyped
value. The pattern alone cannot bound the label/host length (a regex quantifier
would, but the DNS-1123 label form makes that unreadable), so CEL carries it.

Shape validation does NOT restrict WHICH host the operator will dial; the endpoint is
CR-author-controlled, so confining it is an admin egress control (SSRF), not a CRD rule.
Set the operator flag --remote-control-allowed-endpoint-hosts to permit only sanctioned
gNB hosts for the runtime push (empty = any, backward compatible), and pair it with the
operator egress NetworkPolicy (config/network-policy/allow-egress-traffic.yaml). See #299.
maxLength: 261
minLength: 1
pattern: ^(\[[0-9a-fA-F:]+\]|[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?)*):[0-9]{1,5}$
Expand Down
29 changes: 17 additions & 12 deletions internal/controller/ntncellconfig_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -968,21 +968,26 @@ func (r *NTNCellConfigReconciler) resolveRemoteControlTLS(
if err := reader.Get(ctx, client.ObjectKey{Namespace: namespace, Name: t.SecretName}, secret); err != nil {
return nil, "", fmt.Errorf("reading remoteControl.tls secret %q: %w", t.SecretName, err)
}
// SECURITY (partial confused-deputy MITIGATION, not an authorization boundary): the
// operator reads this Secret with its OWN cluster-wide secrets-get, on behalf of
// whoever authored the NTNCellConfig — who may not be able to read Secrets. Two gates
// raise the bar against a CR author pointing the operator at an arbitrary Secret and
// shipping its contents to a CR-controlled endpoint:
// SECURITY (confused deputy): the operator reads this Secret with its OWN cluster-wide
// secrets-get, on behalf of whoever authored the NTNCellConfig — who may not be able to
// read Secrets. The ENFORCED authorization boundary is the credentialRefPolicy
// ValidatingAdmissionPolicy (dist/chart, credentialRefPolicy.enable; ADR-0009, #251):
// when enabled the apiserver requires the CR's writer to hold `get` on the referenced
// Secret, so the writer cannot borrow a Secret it could not read itself. That policy is
// OPT-IN and OFF by default (enable=false), so out of the box the two gates below are a
// PARTIAL mitigation only — NOT an authorization boundary — raising the bar against a CR
// author pointing the operator at an arbitrary Secret and shipping its contents to a
// CR-controlled endpoint:
// (1) refuse a Kubernetes API credential — a service-account or bootstrap token
// Secret stores its apiserver token under the same "token" key we read;
// (2) require the Secret's owner to opt it in via a label.
// Known LIMITS (a real per-CR/per-endpoint SubjectAccessReview or grant resource is a
// tracked follow-up): the opt-in is namespace-scoped — once labelled, ANY NTNCellConfig
// in the namespace may use the Secret; a principal with secrets `patch` but not `get`
// could add the label to a Secret it cannot read; and the type check does not stop an
// Opaque Secret from holding some other bearer token. Those limits are about WHICH Secret
// may be named; the CA-pinning gate further down bounds where its token may be SENT,
// which is what turns "named the wrong Secret" into something less than exfiltration.
// LIMITS of gates (1)/(2) — why they do not substitute for the policy: the label opt-in
// is namespace-scoped — once labelled, ANY NTNCellConfig in the namespace may use the
// Secret; a principal with secrets `patch` but not `get` could add the label to a Secret
// it cannot read; and the type check does not stop an Opaque Secret from holding some
// other bearer token. Those limits are about WHICH Secret may be named; the CA-pinning
// gate further down bounds where its token may be SENT, which is what turns "named the
// wrong Secret" into something less than exfiltration.
switch secret.Type {
case corev1.SecretTypeServiceAccountToken, secretTypeBootstrapToken:
return nil, "", fmt.Errorf("remoteControl.tls secret %q has type %q — a Kubernetes API credential must not be used as a gNB remote-control secret", t.SecretName, secret.Type)
Expand Down
6 changes: 6 additions & 0 deletions nephio/packages/ntn-operators-crds/ntncellconfigs-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,12 @@ spec:
1-63) — a permanent admission error beats a silent tight-requeue on a mistyped
value. The pattern alone cannot bound the label/host length (a regex quantifier
would, but the DNS-1123 label form makes that unreadable), so CEL carries it.

Shape validation does NOT restrict WHICH host the operator will dial; the endpoint is
CR-author-controlled, so confining it is an admin egress control (SSRF), not a CRD rule.
Set the operator flag --remote-control-allowed-endpoint-hosts to permit only sanctioned
gNB hosts for the runtime push (empty = any, backward compatible), and pair it with the
operator egress NetworkPolicy (config/network-policy/allow-egress-traffic.yaml). See #299.
maxLength: 261
minLength: 1
pattern: ^(\[[0-9a-fA-F:]+\]|[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?)*):[0-9]{1,5}$
Expand Down