Skip to content

Commit f19ba73

Browse files
committed
Merge branch 'teleport/configurable-resource-labels' into 'master'
Teleport: make sidecar resource labels configurable Closes #730 See merge request postgres-ai/database-lab!1162
2 parents 1ea3058 + ad19461 commit f19ba73

4 files changed

Lines changed: 438 additions & 45 deletions

File tree

engine/cmd/cli/commands/teleport/SETUP.md

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ spec:
121121
db_users: ['*']
122122
```
123123
124+
To gate access on your own taxonomy instead of `dblab: "true"`, attach custom
125+
labels with `--label` and match on those — see [Resource Labels](#resource-labels).
126+
124127
### 5. SSL/TLS for Postgres Clones
125128

126129
Teleport **always** initiates TLS to backend databases, even when `tls.mode: insecure`
@@ -275,9 +278,78 @@ dblab teleport serve \
275278
--listen-addr 0.0.0.0:9876 \
276279
--dblab-url http://localhost:2345 \
277280
--dblab-token "$DBLAB_TOKEN" \
278-
--webhook-secret "$WEBHOOK_SECRET"
281+
--webhook-secret "$WEBHOOK_SECRET" \
282+
--label environment=production \
283+
--label db-type=main \
284+
--label service=dblab
279285
```
280286

287+
## Resource Labels
288+
289+
Every Teleport `db` and `app` resource the sidecar creates carries a set of
290+
labels. Some are managed by the sidecar and cannot be overridden; the rest are
291+
supplied by the operator to match an existing Teleport taxonomy.
292+
293+
**Managed (reserved) labels — always set:**
294+
295+
| Label | Value | Purpose |
296+
|-------|-------|---------|
297+
| `dblab` | `"true"` | Marks the resource as DBLab-managed; used by the agent matcher and user roles |
298+
| `dblab_instance` | `<environment-id>` | Owning DBLab instance; used internally to keep reconciliation isolated per instance |
299+
| `clone_id` | clone ID | DB resources only |
300+
| `dblab_user` | clone username | DB resources only, when known |
301+
| `environment` | `<environment-id>` | Set **only** when no custom `environment` label is provided (backwards compatibility) |
302+
303+
> **Important:** Each DBLab instance must use a **unique** `--environment-id`.
304+
> It becomes the `dblab_instance` ownership label, and instances that share one
305+
> would reconcile each other's resources.
306+
307+
**Custom labels — `--label key=value` (repeatable):**
308+
309+
Use `--label` to attach any additional labels so DBLab clones fit the same
310+
access taxonomy as other database resources in your cluster — keys and values
311+
are entirely operator-defined. Labels can also be supplied via the
312+
`TELEPORT_LABELS` environment variable (comma-separated, e.g.
313+
`TELEPORT_LABELS=environment=production,db-type=main`). Reserved keys
314+
(`dblab`, `dblab_instance`, `clone_id`, `dblab_user`) are rejected at startup.
315+
316+
Because instance ownership is tracked by the dedicated `dblab_instance` label,
317+
the `environment` label is free for operator use. Multiple DBLab instances can
318+
therefore share the same `environment` value (e.g. `production`) and be
319+
distinguished by `db-type` while each `--environment-id` keeps reconciliation
320+
isolated:
321+
322+
```bash
323+
# instance serving the main OLTP database
324+
dblab teleport serve --environment-id production-main \
325+
--label environment=production --label db-type=main --label service=dblab ...
326+
327+
# instance serving the analytics database
328+
dblab teleport serve --environment-id production-analytics \
329+
--label environment=production --label db-type=analytics --label service=dblab ...
330+
```
331+
332+
A user/bot role can then gate access on the stable labels, with the rest as
333+
wildcards:
334+
335+
```yaml
336+
spec:
337+
allow:
338+
db_labels:
339+
service: ["dblab"]
340+
environment: ["*"]
341+
db-type: ["*"]
342+
app_labels:
343+
service: ["dblab"]
344+
environment: ["*"]
345+
db-type: ["*"]
346+
```
347+
348+
> **Note:** Teleport requires the resource to carry **every** label key named in
349+
> a role's `db_labels`/`app_labels`. If a role lists a key (e.g. `writable`) that
350+
> the sidecar does not set, add it with `--label writable=readwrite`, otherwise
351+
> access is denied.
352+
281353
## Connecting to a Clone
282354

283355
Once everything is running, users connect through Teleport:

engine/cmd/cli/commands/teleport/serve.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"net/http"
1515
"os"
1616
"os/signal"
17+
"strings"
1718
"sync"
1819
"syscall"
1920
"time"
@@ -45,6 +46,7 @@ type Config struct {
4546
DblabURL string
4647
DblabToken string
4748
WebhookSecret string
49+
Labels map[string]string
4850
}
4951

5052
// service holds runtime state for the teleport sidecar.
@@ -120,13 +122,56 @@ func CommandList() []*cli.Command {
120122
Required: true,
121123
EnvVars: []string{"WEBHOOK_SECRET"},
122124
},
125+
&cli.StringSliceFlag{
126+
Name: "label",
127+
Usage: "additional Teleport resource label in key=value form (repeatable)",
128+
EnvVars: []string{"TELEPORT_LABELS"},
129+
},
123130
},
124131
},
125132
},
126133
}}
127134
}
128135

136+
// parseLabels converts repeated key=value flag entries into a label map,
137+
// rejecting malformed entries and keys reserved by the sidecar.
138+
func parseLabels(entries []string) (map[string]string, error) {
139+
labels := make(map[string]string, len(entries))
140+
141+
for _, entry := range entries {
142+
key, value, found := strings.Cut(entry, "=")
143+
if !found || key == "" || value == "" {
144+
return nil, fmt.Errorf("invalid label %q: expected non-empty key=value", entry)
145+
}
146+
147+
if _, dup := labels[key]; dup {
148+
return nil, fmt.Errorf("duplicate label %q", key)
149+
}
150+
151+
if reservedLabels[key] {
152+
return nil, fmt.Errorf("label %q is reserved and managed by the sidecar", key)
153+
}
154+
155+
if _, err := sanitizeLabelKey(key); err != nil {
156+
return nil, err
157+
}
158+
159+
if _, err := sanitizeYAMLValue(value, key); err != nil {
160+
return nil, err
161+
}
162+
163+
labels[key] = value
164+
}
165+
166+
return labels, nil
167+
}
168+
129169
func serveAction(c *cli.Context) error {
170+
labels, err := parseLabels(c.StringSlice("label"))
171+
if err != nil {
172+
return err
173+
}
174+
130175
cfg := &Config{
131176
ListenAddr: c.String("listen-addr"),
132177
EnvironmentID: c.String("environment-id"),
@@ -136,6 +181,7 @@ func serveAction(c *cli.Context) error {
136181
DblabURL: c.String("dblab-url"),
137182
DblabToken: c.String("dblab-token"),
138183
WebhookSecret: c.String("webhook-secret"),
184+
Labels: labels,
139185
}
140186

141187
if cfg.WebhookSecret == "" {

0 commit comments

Comments
 (0)