<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Oidc on inetshell</title><link>https://inet.sh/es/tags/oidc/</link><description>Recent content in Oidc on inetshell</description><generator>Hugo</generator><language>es</language><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/es/tags/oidc/index.xml" rel="self" type="application/rss+xml"/><item><title>GitHub Actions → Google Cloud sin llaves: Workload Identity Federation con gcloud y Terraform</title><link>https://inet.sh/es/posts/gcp/github-actions-workload-identity-federation/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/gcp/github-actions-workload-identity-federation/</guid><description>Autenticar un workflow de GitHub Actions en Google Cloud sin guardar una llave JSON de service account: pool y proveedor OIDC de Workload Identity Federation, attribute condition que limita a tu organización o repo, binding workloadIdentityUser, y el paso google-github-actions/auth@v3 con permissions id-token: write. La misma configuración en Terraform con el módulo gh-oidc, y los errores típicos de permisos.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PROJECT_ID</span><span class="o">=</span>my-project
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PROJECT_NUMBER</span><span class="o">=</span><span class="k">$(</span>gcloud projects describe <span class="nv">$PROJECT_ID</span> --format<span class="o">=</span><span class="s1">&#39;value(projectNumber)&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">GITHUB_ORG</span><span class="o">=</span>my-org
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">REPO</span><span class="o">=</span>my-org/my-repo
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gcloud iam workload-identity-pools create github <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --project<span class="o">=</span><span class="nv">$PROJECT_ID</span> --location<span class="o">=</span>global --display-name<span class="o">=</span><span class="s2">&#34;GitHub Actions&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gcloud iam workload-identity-pools providers create-oidc github-oidc <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --project<span class="o">=</span><span class="nv">$PROJECT_ID</span> --location<span class="o">=</span>global --workload-identity-pool<span class="o">=</span>github <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --display-name<span class="o">=</span><span class="s2">&#34;GitHub OIDC&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --issuer-uri<span class="o">=</span><span class="s2">&#34;https://token.actions.githubusercontent.com&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --attribute-mapping<span class="o">=</span><span class="s2">&#34;google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --attribute-condition<span class="o">=</span><span class="s2">&#34;assertion.repository_owner == &#39;</span><span class="nv">$GITHUB_ORG</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gcloud iam service-accounts create github-actions --project<span class="o">=</span><span class="nv">$PROJECT_ID</span> --display-name<span class="o">=</span><span class="s2">&#34;GitHub Actions&#34;</span>
</span></span><span class="line"><span class="cl">gcloud iam service-accounts add-iam-policy-binding github-actions@<span class="nv">$PROJECT_ID</span>.iam.gserviceaccount.com <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --project<span class="o">=</span><span class="nv">$PROJECT_ID</span> --role<span class="o">=</span>roles/iam.workloadIdentityUser <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --member<span class="o">=</span><span class="s2">&#34;principalSet://iam.googleapis.com/projects/</span><span class="nv">$PROJECT_NUMBER</span><span class="s2">/locations/global/workloadIdentityPools/github/attribute.repository/</span><span class="nv">$REPO</span><span class="s2">&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Y en el workflow:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">deploy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">permissions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="l">read</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">id-token</span><span class="p">:</span><span class="w"> </span><span class="l">write           </span><span class="w"> </span><span class="c"># sin esto GitHub no emite el token OIDC</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">auth</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">google-github-actions/auth@v3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">workload_identity_provider</span><span class="p">:</span><span class="w"> </span><span class="l">projects/123456789012/locations/global/workloadIdentityPools/github/providers/github-oidc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service_account</span><span class="p">:</span><span class="w"> </span><span class="l">github-actions@my-project.iam.gserviceaccount.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">google-github-actions/setup-gcloud@v3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">gcloud storage ls</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Los roles que necesite el pipeline (Artifact Registry, Cloud Run, GKE…) se los das a <code>github-actions@…</code> como a cualquier service account.</p>
<h2 id="por-qué">Por qué</h2>
<p>La alternativa clásica es exportar una llave JSON del service account y guardarla como secreto en GitHub. Esa llave no caduca, cualquiera con acceso al repo o a los logs puede copiarla, y rotarla es un trabajo manual que nadie hace. Con <strong>Workload Identity Federation</strong> GitHub emite un token OIDC de corta vida para cada job, Google lo verifica contra el issuer de GitHub, comprueba tu <em>attribute condition</em> y entrega credenciales temporales del service account. No hay secreto que guardar ni rotar, y puedes limitar quién entra hasta el nivel de repo o rama.</p>
<h2 id="las-piezas">Las piezas</h2>
<table>
  <thead>
      <tr>
          <th>Pieza</th>
          <th>Qué es</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Pool</strong> (<code>github</code>)</td>
          <td>Contenedor de identidades externas; uno por proveedor basta</td>
      </tr>
      <tr>
          <td><strong>Proveedor OIDC</strong> (<code>github-oidc</code>)</td>
          <td>Confía en <code>https://token.actions.githubusercontent.com</code> y mapea claims del token (<code>repository</code>, <code>repository_owner</code>, <code>actor</code>, <code>ref</code>) a atributos</td>
      </tr>
      <tr>
          <td><strong>Attribute condition</strong></td>
          <td>Filtro CEL sobre esos claims. Sin ella, <strong>cualquier repo de GitHub</strong> podría intentar entrar al pool; el README oficial pide ponerla siempre</td>
      </tr>
      <tr>
          <td><strong>Binding <code>workloadIdentityUser</code></strong></td>
          <td>Qué identidades del pool pueden hacerse pasar por el service account: <code>attribute.repository/my-org/my-repo</code>, o <code>attribute.repository_owner/my-org</code> para toda la organización</td>
      </tr>
      <tr>
          <td><strong><code>auth@v3</code></strong></td>
          <td>Cambia el token de GitHub por credenciales de Google y las deja configuradas para <code>gcloud</code>, los SDK y Terraform</td>
      </tr>
  </tbody>
</table>
<p>Para restringir a una rama, mapea el claim y úsalo en la condición y en el binding:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">--attribute-mapping=&#34;…,attribute.ref=assertion.ref&#34;
</span></span><span class="line"><span class="cl">--attribute-condition=&#34;assertion.repository_owner == &#39;my-org&#39; &amp;&amp; assertion.ref == &#39;refs/heads/main&#39;&#34;
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="con-terraform">Con Terraform</h2>
<p>El módulo oficial hace pool, proveedor y bindings en un bloque:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">module</span> <span class="s2">&#34;github_actions_sa&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  source</span>     <span class="o">=</span> <span class="s2">&#34;terraform-google-modules/service-accounts/google&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  version</span>    <span class="o">=</span> <span class="s2">&#34;~&gt; 4.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  project_id</span> <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">project_id</span>
</span></span><span class="line"><span class="cl"><span class="n">  names</span>      <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;github-actions&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">  project_roles</span> <span class="o">=</span><span class="n"> [&#34;${var.project_id}</span><span class="o">=</span><span class="err">&gt;</span><span class="k">roles</span><span class="err">/</span><span class="k">artifactregistry</span><span class="p">.</span><span class="k">writer</span><span class="err">&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="s2">&#34;gh_oidc&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  source</span>              <span class="o">=</span> <span class="s2">&#34;terraform-google-modules/github-actions-runners/google//modules/gh-oidc&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  project_id</span>          <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">project_id</span>
</span></span><span class="line"><span class="cl"><span class="n">  pool_id</span>             <span class="o">=</span> <span class="s2">&#34;github&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  provider_id</span>         <span class="o">=</span> <span class="s2">&#34;github-oidc&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  attribute_condition</span> <span class="o">=</span><span class="n"> &#34;assertion.repository_owner</span> <span class="o">==</span> <span class="err">&#39;</span><span class="k">my</span><span class="err">-</span><span class="k">org</span><span class="err">&#39;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  sa_mapping</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">    &#34;github-actions&#34;</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">      sa_name</span>   <span class="o">=</span> <span class="s2">&#34;projects/${var.project_id}/serviceAccounts/${module.github_actions_sa.email}&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">      attribute</span> <span class="o">=</span> <span class="s2">&#34;attribute.repository/my-org/my-repo&#34;</span>
</span></span><span class="line"><span class="cl">    }
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">output &#34;workload_identity_provider&#34; { value</span> <span class="o">=</span> <span class="k">module</span><span class="p">.</span><span class="k">gh_oidc</span><span class="p">.</span><span class="k">provider_name</span> }
</span></span></code></pre></td></tr></table>
</div>
</div><p>El <code>output</code> es exactamente el valor que va en <code>workload_identity_provider</code> del workflow.</p>
<h2 id="sin-service-account">Sin service account</h2>
<p><code>auth@v3</code> también admite <strong>federación directa</strong>: el <code>principalSet</code> recibe roles IAM sin pasar por un service account (<code>workload_identity_provider</code> y <code>project_id</code>, sin <code>service_account</code>). Menos piezas, pero no todos los servicios aceptan principales federados; para Artifact Registry, GCS y la mayoría de APIs sí, y es el camino que Google recomienda para casos nuevos.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong><code>id-token: write</code></strong> en <code>permissions</code> del job. Sin él, <code>auth</code> falla con <em>&ldquo;Unable to get ACTIONS_ID_TOKEN_REQUEST_URL&rdquo;</em>.</li>
<li><strong>Número de proyecto, no ID</strong>, en la ruta del proveedor y del <code>principalSet</code>: <code>projects/123456789012/…</code>.</li>
<li><strong><code>Permission 'iam.serviceAccounts.getAccessToken' denied</code></strong>: falta el binding <code>roles/iam.workloadIdentityUser</code> o el <code>attribute.repository</code> no coincide (mayúsculas y nombre exacto del repo).</li>
<li><strong>La condición debe referirse a claims mapeados</strong>: si usas <code>assertion.ref</code> en la condición sin haberlo mapeado a un atributo, el proveedor se crea pero nunca casa.</li>
<li><strong>Forks y PRs externos</strong>: los tokens de un PR desde un fork llevan <code>repository</code> del fork; una condición por <code>repository_owner</code> los deja fuera, que suele ser lo que quieres.</li>
<li><strong>Rotación</strong>: no hay nada que rotar, pero sí que revisar. <code>gcloud iam workload-identity-pools providers describe</code> y los logs de auditoría de STS dicen quién está entrando.</li>
</ul>
<p>Referencia: <a href="https://github.com/google-github-actions/auth">google-github-actions/auth</a> y <a href="https://docs.cloud.google.com/iam/docs/workload-identity-federation">Workload Identity Federation</a> en la documentación de Google Cloud.</p>
]]></content:encoded></item><item><title>Proxmox VE con Keycloak: login por OpenID Connect (realm openid)</title><link>https://inet.sh/es/posts/proxmox/keycloak-openid-connect/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/proxmox/keycloak-openid-connect/</guid><description>Autenticar en Proxmox VE con Keycloak usando un realm de tipo OpenID Connect: cliente confidencial en Keycloak 26, pveum realm add con issuer-url, client-id, client-key y username-claim preferred_username, usuarios autocreados, permisos y grupos. Sin mappers extra ni /auth/ en la URL.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<p>En Keycloak: <strong>Clients → Create client</strong> → OpenID Connect, Client ID <code>proxmox</code>, <em>Client authentication</em> <strong>On</strong>, <em>Standard flow</em> <strong>On</strong>, <em>Valid redirect URIs</em> <code>https://pve.example.local:8006</code> y <code>https://pve.example.local:8006/*</code>. Copia el secret de la pestaña <strong>Credentials</strong>.</p>
<p>En un nodo Proxmox:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pveum realm add keycloak --type openid <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --issuer-url https://id.example.local/realms/lab <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --client-id proxmox <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --client-key <span class="s1">&#39;&lt;client-secret&gt;&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --username-claim preferred_username <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --autocreate <span class="m">1</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --comment <span class="s1">&#39;Keycloak&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Los usuarios entran como <code>alice@keycloak</code>. Se crean solos al primer login, <strong>sin permisos</strong>; los permisos siguen siendo ACLs de Proxmox:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pveum group add sso-admins
</span></span><span class="line"><span class="cl">pveum acl modify / --groups sso-admins --roles Administrator
</span></span><span class="line"><span class="cl">pveum user modify alice@keycloak --groups sso-admins     <span class="c1"># después de su primer login</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="cómo-funciona">Cómo funciona</h2>
<p>Proxmox soporta realms <code>openid</code> desde PVE 7. Con <code>issuer-url</code> descarga <code>/.well-known/openid-configuration</code> y de ahí saca los endpoints; en la pantalla de login aparece el realm en el desplegable y el botón <strong>Login</strong> redirige a Keycloak. Al volver, el valor del claim que elegiste en <code>username-claim</code> se convierte en el usuario <code>&lt;claim&gt;@&lt;realm&gt;</code>.</p>
<p><code>username-claim</code> acepta cualquier nombre de claim. Keycloak manda <code>preferred_username</code> por defecto (viene del mapper <em>username</em> del client scope <code>profile</code>), así que <strong>no hace falta el mapper adicional</strong> que traían las guías antiguas para exponer un claim llamado <code>username</code>. Si prefieres el correo, <code>--username-claim email</code>.</p>
<h2 id="keycloak-paso-a-paso-26">Keycloak, paso a paso (26)</h2>
<ol>
<li><strong>Realm.</strong> Usa el que tengas (<code>lab</code> en el ejemplo). El issuer es <code>https://id.example.local/realms/lab</code>. Desde Keycloak 17 ya no hay <code>/auth/</code> en la ruta; si copias <code>/auth/realms/...</code> de una guía vieja, el discovery falla.</li>
<li><strong>Clients → Create client.</strong>
<ul>
<li><em>Client type</em>: OpenID Connect. <em>Client ID</em>: <code>proxmox</code>. <em>Name</em>: lo que quieras.</li>
<li><em>Capability config</em>: <strong>Client authentication On</strong> (cliente confidencial, con secret). <strong>Standard flow On</strong>. Todo lo demás Off: <em>Direct access grants</em>, <em>Implicit flow</em>, <em>Service accounts</em>, <em>Authorization</em>.</li>
<li><em>Login settings</em>: <em>Root URL</em> <code>https://pve.example.local:8006</code>; <em>Valid redirect URIs</em> <code>https://pve.example.local:8006</code> y <code>https://pve.example.local:8006/*</code>. <em>Web origins</em> vacío: es un flujo de redirección, no hay CORS.</li>
</ul>
</li>
<li><strong>Credentials</strong> → <em>Client secret</em>. Es el <code>--client-key</code>.</li>
<li><strong>Grupos (opcional).</strong> Clients → <code>proxmox</code> → <em>Client scopes</em> → <code>proxmox-dedicated</code> → <em>Add mapper</em> → <em>By configuration</em> → <strong>Group Membership</strong>: <em>Name</em> <code>groups</code>, <em>Token Claim Name</em> <code>groups</code>, <em>Full group path</em> Off, <em>Add to ID token</em> y <em>Add to userinfo</em> On.</li>
</ol>
<h2 id="proxmox-paso-a-paso">Proxmox, paso a paso</h2>
<p>Por GUI: <strong>Datacenter → Permissions → Realms → Add → OpenID Connect Server</strong>: <em>Issuer URL</em>, <em>Realm</em> (el nombre corto, <code>keycloak</code>), <em>Client ID</em>, <em>Client Key</em>, <em>Username Claim</em> <code>preferred_username</code>, <em>Autocreate Users</em>. O el <code>pveum realm add</code> del En corto; la configuración queda en <code>/etc/pve/domains.cfg</code> y se replica al clúster.</p>
<p>Con grupos desde Keycloak, en versiones recientes de PVE 8 y en PVE 9 (<code>pveum realm add --help</code> te dice si tu versión tiene <code>--groups-claim</code>):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pveum realm modify keycloak --groups-claim groups --groups-autocreate <span class="m">1</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Cada grupo del claim se crea como <code>&lt;grupo&gt;-keycloak</code> y el usuario entra en ellos en cada login; los ACLs los asignas a esos grupos.</p>
<p>Prueba antes de tocar nada más: pantalla de login → Realm <strong>Keycloak</strong> → <strong>Login</strong> → vuelves autenticado y <code>pveum user list</code> muestra <code>alice@keycloak</code>.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong><code>Invalid parameter: redirect_uri</code></strong> en Keycloak: la URL con la que entras al navegador (nombre, puerto <code>8006</code>, <code>https</code>) no está en <em>Valid redirect URIs</em>. Si usas la IP y el FQDN, agrega las dos.</li>
<li><strong>El certificado de Keycloak lo tiene que aceptar el nodo</strong>, no solo tu navegador. Con una CA interna: copia el <code>.crt</code> a <code>/usr/local/share/ca-certificates/</code> y <code>update-ca-certificates</code>.</li>
<li><strong><code>issuer-url</code> tiene que coincidir exactamente</strong> con el <code>iss</code> de los tokens: <code>https</code>, sin barra final, <code>/realms/&lt;realm&gt;</code>.</li>
<li><strong>Hora.</strong> Un reloj desfasado invalida los tokens; <code>chrony</code> en los nodos.</li>
<li><strong>No dejes que el usuario edite su propio username</strong> en Keycloak (aviso de la propia documentación de Proxmox): cambiarse el nombre a <code>root</code> es cambiarse de cuenta en Proxmox.</li>
<li><strong>No pongas <code>--default 1</code> hasta que funcione</strong>, y deja <code>root@pam</code> como puerta de emergencia: si Keycloak cae, el realm <code>openid</code> no puede autenticar a nadie.</li>
<li><strong>API y CLI.</strong> Los usuarios OIDC no tienen contraseña en Proxmox; para Terraform, Ansible o <code>pvesh</code> desde fuera crea un token: <code>pveum user token add alice@keycloak automation --privsep 0</code>.</li>
</ul>
<p>Referencia: <a href="https://pve.proxmox.com/pve-docs/chapter-pveum.html#user-realms-oidc">User Management — OpenID Connect</a> en la guía de Proxmox y la <a href="https://www.keycloak.org/docs/latest/server_admin/index.html#_oidc_clients">creación de clientes</a> en la documentación de Keycloak.</p>
]]></content:encoded></item></channel></rss>