# BrokerMembership

> Reference for the nauthera.io/v1alpha1 BrokerMembership resource: one side of an identity broker membership, declared from that side's organization.

A `BrokerMembership` declares **one side** of an [identity broker](https://www.nauthera.io/docs/v1/identity-broker)
membership, from a namespace of that side's organization. The broker's side names the
member it invites; the member's side names the broker it accepts. The membership is
active while both sides exist, and deleting either one ends it.

| Property | Value |
|---|---|
| API group / version | `nauthera.io/v1alpha1` |
| Kind | `BrokerMembership` |
| Plural / short name | `brokermemberships` / `bm` |
| Scope | Namespaced |
| Definition | `deploy/crds/brokermembership-crd.yaml` in nauthera-server |

## Example

The main organization (`default`) brokers `acme`, and admits it to one of its clients:

```yaml
# The broker's side, in a namespace the main organization admits.
apiVersion: nauthera.io/v1alpha1
kind: BrokerMembership
metadata:
  name: acme
  namespace: nauthera
spec:
  member: acme
  clients: [grafana]          # optional; omitted admits every client of the broker
---
# The member's side, in a namespace acme admits.
apiVersion: nauthera.io/v1alpha1
kind: BrokerMembership
metadata:
  name: nauthera
  namespace: acme
spec:
  broker: default
```

`kubectl get bm -A` shows both sides with the membership's state:

```text
NAMESPACE   NAME       MEMBER   BROKER    STATE    PHASE   READY   REASON       AGE
acme        nauthera            default   active   Ready   True    Registered   3m
nauthera    acme       acme               active   Ready   True    Registered   4m
```

## Behavior

- **One side per resource.** Exactly one of `spec.member` and `spec.broker` is set:
  `member` makes the resource the broker's invitation, `broker` makes it the member's
  acceptance. Both, and the organization the side is declared for, are fixed after
  creation.
- **Each side from its own organization.** Which organization a side is declared for
  follows from its namespace, as for every namespaced resource (see
  [Which organization a resource configures](https://www.nauthera.io/docs/v1/operator#which-organization-a-resource-configures)).
  The server also holds each side to the operator's grant for that organization, so a
  team that cannot write in the other organization's namespaces cannot consent for it.
- **Either order.** A member's side applied first waits as `AwaitingBroker` and is sent
  again every minute. The broker's side is `Ready` with `AwaitingMember` until the member
  accepts; it has done its part. A side created or deleted in the same cluster has every
  other side sent again at once; one in another cluster notices within ten minutes.
- **The broker's clients.** `spec.clients` lists the `client_id`s of the broker's clients
  the membership admits, and only those route to the member. Editing it changes them at
  once. A client the membership does not admit never offers the member: a broker can
  admit its members to Grafana and keep its Argo CD for its own users.
- **Accepting provisions a client at the member.** It is the reserved client
  `{broker}-broker` the broker signs the member's users in through. Ending the membership
  removes it.
- **Managed here.** What a resource declares cannot be changed or ended from the
  [admin dashboard](https://www.nauthera.io/docs/v1/admin-dashboard) or the admin API, which answer `409` with
  the audit cause `operator_managed`. Change the resource instead.

### Deleting a resource

Deleting either side ends the membership. A finalizer (`nauthera.io/brokermembership`)
holds the resource until the server has withdrawn its side; one the server did not take
keeps the resource in phase `Deleting` with reason `MembershipNotEnded`, and is retried.
If the other side still exists, the broker's side invites again, and the membership is
`pending` until the member accepts once more.

## Spec

{/* AUTO-GENERATED-SPEC-START */}

### `spec.broker`

**Type:** `string` | **Optional**

The slug of the broker accepted, when this resource is the member's
side. Immutable.

### `spec.clients`

**Type:** `[]string` | **Optional**

The client_ids of the broker's clients the membership admits, with
spec.member only. Empty admits every client of the broker.

### `spec.member`

**Type:** `string` | **Optional**

The slug of the organization invited, when this resource is the
broker's side. Immutable.

### `spec.organization`

**Type:** `string` | **Optional**

The slug of the organization this side belongs to. Needed only
where more than one organization admits the namespace in its
spec.namespaces; otherwise the one that does is taken. The main
organization is "default". Immutable.

{/* AUTO-GENERATED-SPEC-END */}

## Status

`status.state` is the membership's state on the server, `pending` or `active`, and empty
while the server holds none. `phase` is one of `Pending`, `Ready`, `Error` or `Deleting`.
Beside the connection reasons every resource shares (`Refused`, `Unreachable`,
`Unsupported`, `CredentialUnavailable`, `TrustAnchorUnavailable`; see the
[OIDCClient reference](https://www.nauthera.io/docs/v1/oidc-client#status)) and the namespace reasons
(`NamespaceNotCovered`, `NamespaceNotAdmitted`, `OrganizationAmbiguous`,
`OrganizationNotReady`, `OrganizationChanged`), the `Ready` condition's reason is one of:

| Reason | Phase | Meaning |
|---|---|---|
| `Registered` | `Ready` | The membership is active. |
| `AwaitingMember` | `Ready` | The broker's invitation is recorded and the member has not accepted it. |
| `AwaitingBroker` | `Pending` | The member's acceptance names a broker that has not invited it. Sent again every minute. |
| `MembershipNotEnded` | `Deleting` | The resource is being deleted and the server has not ended the membership. |

Each failure reason has a [runbook](https://www.nauthera.io/docs/v1/runbooks).

{/* AUTO-GENERATED-STATUS-START */}

| Field | Type | Description |
|---|---|---|
| `acceptedAt` | `string` | When the member accepted. |
| `conditions` | `[]object` | Standard Kubernetes conditions (Ready, ...). |
| `lastReconciledTime` | `string` | When the operator last reconciled the resource, whatever the outcome. A stale value means the operator is not running, not the leader, or cannot see the resource. |
| `observedGeneration` | `integer` | .metadata.generation last reconciled by the server. |
| `organization` | `string` | The slug of the organization this side was declared for. Once set, a namespace that comes to configure another organization is refused rather than moving the side. |
| `phase` | `string` | High-level lifecycle phase. |
| `state` | `string` | The membership's state on the server: pending until the member accepts, then active. Empty while the server holds none. |

{/* AUTO-GENERATED-STATUS-END */}

## Related

- [Identity broker](https://www.nauthera.io/docs/v1/identity-broker): what a broker is, and setting one up
- [Broker sign-in](https://www.nauthera.io/docs/v1/sign-in-flows/broker): what users see
- [Organization](https://www.nauthera.io/docs/v1/organization-resource): `spec.namespaces` and `spec.emailDomains`
- [Operator](https://www.nauthera.io/docs/v1/operator#who-may-change-what): the grant and the team roles
