<?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>Provisioning on inetshell</title><link>https://inet.sh/es/tags/provisioning/</link><description>Recent content in Provisioning 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/provisioning/index.xml" rel="self" type="application/rss+xml"/><item><title>Grafana con SSO: entrar con el usuario local (?disableAutoLogin), roles que se pisan en cada login (skip_org_role_sync) y datasources aprovisionados con secretos</title><link>https://inet.sh/es/posts/monitoring/grafana-sso-disableautologin-roles-provisioning/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/monitoring/grafana-sso-disableautologin-roles-provisioning/</guid><description>Tres cosas que acaban mordiendo en cualquier Grafana con OAuth: auto_login te manda al proveedor antes de ver el formulario y te deja fuera si el SSO se rompe (la salida es /login?disableAutoLogin=true), los roles que asignas a mano vuelven a su valor en el siguiente login salvo que uses skip_org_role_sync o mapees bien role_attribute_path, y los datasources aprovisionados por YAML que no se pueden editar en la UI, guardan secretos en secureJsonData y solo desaparecen con deleteDatasources o prune.</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">https://grafana.example.com/login?disableAutoLogin=true
</span></span></code></pre></td></tr></table>
</div>
</div><p>Eso te lleva al formulario de usuario y contraseña aunque <code>auto_login = true</code> te esté redirigiendo al proveedor OAuth. Si además no recuerdas la contraseña del admin local:</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></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="c1"># contenedor / pod</span>
</span></span><span class="line"><span class="cl">grafana cli --homepath /usr/share/grafana admin reset-admin-password <span class="s1">&#39;nueva&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># paquete</span>
</span></span><span class="line"><span class="cl">grafana-cli admin reset-admin-password <span class="s1">&#39;nueva&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Y si el formulario está desactivado (<code>[auth] disable_login_form = true</code>), primero <code>GF_AUTH_DISABLE_LOGIN_FORM=false</code> en el entorno y reinicia.</p>
<h2 id="roles-que-se-deshacen-solos">Roles que se deshacen solos</h2>
<p>Le das <em>Editor</em> a alguien en <em>Administration → Users</em>, y al día siguiente vuelve a ser <em>Viewer</em>. No es un bug: en cada login OAuth Grafana <strong>sincroniza el rol de la organización</strong> con lo que diga el proveedor (o con <code>auto_assign_org_role</code> si el proveedor no dice nada) y pisa lo que pusiste a mano.</p>
<p>Dos formas de vivir con ello:</p>
<p><strong>1. Que el proveedor mande de verdad.</strong> Mapea grupos o claims a roles con JMESPath y haz que sea estricto, para que un usuario sin grupo no entre como Viewer por defecto:</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[auth.github]</span>
</span></span><span class="line"><span class="cl"><span class="na">enabled</span> <span class="o">=</span> <span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">allow_sign_up</span> <span class="o">=</span> <span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">auto_login</span> <span class="o">=</span> <span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">client_id</span> <span class="o">=</span> <span class="s">$__file{/etc/secrets/github-client-id}</span>
</span></span><span class="line"><span class="cl"><span class="na">client_secret</span> <span class="o">=</span> <span class="s">$__file{/etc/secrets/github-client-secret}</span>
</span></span><span class="line"><span class="cl"><span class="na">scopes</span> <span class="o">=</span> <span class="s">user:email,read:org</span>
</span></span><span class="line"><span class="cl"><span class="na">allowed_organizations</span> <span class="o">=</span> <span class="s">my-org</span>
</span></span><span class="line"><span class="cl"><span class="na">role_attribute_strict</span> <span class="o">=</span> <span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">allow_assign_grafana_admin</span> <span class="o">=</span> <span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">role_attribute_path</span> <span class="o">=</span> <span class="s">contains(groups[*], &#39;@my-org/grafana-admin&#39;) &amp;&amp; &#39;GrafanaAdmin&#39; || contains(groups[*], &#39;@my-org/grafana-editor&#39;) &amp;&amp; &#39;Editor&#39; || contains(groups[*], &#39;@my-org/grafana-viewer&#39;) &amp;&amp; &#39;Viewer&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>2. Que Grafana no toque los roles.</strong> Si prefieres gestionarlos en la UI:</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-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[auth.github]</span>
</span></span><span class="line"><span class="cl"><span class="na">skip_org_role_sync</span> <span class="o">=</span> <span class="s">true</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>skip_org_role_sync</code> va <strong>por proveedor</strong> (<code>auth.github</code>, <code>auth.google</code>, <code>auth.generic_oauth</code>, <code>auth.azuread</code>, <code>auth.okta</code>, <code>auth.gitlab</code>, SAML). El antiguo interruptor global <code>[auth] oauth_skip_org_role_update_sync</code> está obsoleto; si todavía lo tienes en el <code>.ini</code>, cámbialo por el de cada proveedor.</p>
<p>Con Google, <code>hosted_domain</code> y <code>allowed_domains</code> limitan quién puede entrar; sin <code>role_attribute_path</code> todos entran con <code>auto_assign_org_role</code> (Viewer por defecto).</p>
<h2 id="datasources-aprovisionados">Datasources aprovisionados</h2>
<p>Los datasources definidos en <code>provisioning/datasources/*.yaml</code> (en el chart de Helm, <code>datasources.datasources.yaml</code>) tienen tres propiedades que sorprenden:</p>
<ul>
<li><strong>No se editan en la UI</strong> si llevan <code>editable: false</code>: el formulario aparece bloqueado. Es lo correcto en producción; el cambio va en el YAML.</li>
<li><strong>Los secretos van en <code>secureJsonData</code></strong>, no en <code>jsonData</code>: Grafana los cifra con <code>secret_key</code> antes de guardarlos en su base de datos (<code>/var/lib/grafana/grafana.db</code>, SQLite por defecto) y no los devuelve por la API. Tokens, contraseñas y certificados de cliente van ahí; en <code>jsonData</code> quedan en claro.</li>
<li><strong>No desaparecen al quitarlos del YAML.</strong> Grafana solo inserta y actualiza; para borrar hay que decirlo:</li>
</ul>
<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><span class="lnt">24
</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">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">prune: true                  # versiones recientes</span><span class="p">:</span><span class="w"> </span><span class="l">borra lo aprovisionado que ya no esté en este archivo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">deleteDatasources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Sentry-old</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">orgId</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">datasources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Prometheus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">prometheus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">access</span><span class="p">:</span><span class="w"> </span><span class="l">proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">orgId</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="l">http://prometheus-server.monitoring.svc.cluster.local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">isDefault</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">editable</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Sentry</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">grafana-sentry-datasource</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">access</span><span class="p">:</span><span class="w"> </span><span class="l">proxy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">orgId</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">editable</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">jsonData</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="l">https://sentry.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">orgSlug</span><span class="p">:</span><span class="w"> </span><span class="l">my-org</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">secureJsonData</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">authToken</span><span class="p">:</span><span class="w"> </span><span class="l">${SENTRY_TOKEN}     </span><span class="w"> </span><span class="c"># variable de entorno del proceso de Grafana</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Las variables de entorno se expanden con <code>$VAR</code> o <code>${VAR}</code>; un <code>$</code> literal en un secreto se escribe <code>$$</code>.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong><code>auto_login</code> con varios proveedores</strong> se ignora: solo funciona con uno configurado para auto-login.</li>
<li><strong><code>allow_sign_up = false</code></strong> deja fuera a cualquier usuario nuevo del proveedor, incluido el que acabas de dar de alta en el grupo; el error en el log es <em>&ldquo;user signup disabled&rdquo;</em>.</li>
<li><strong><code>role_attribute_strict = true</code> sin grupo mapeado</strong> rechaza el login con <em>&ldquo;Role attribute not found&rdquo;</em>; es lo que quieres, pero avisa a la gente.</li>
<li><strong>El admin local sigue existiendo</strong> con SSO: cámbiale la contraseña y guárdala en el gestor de secretos, es tu puerta de emergencia junto con <code>disableAutoLogin</code>.</li>
<li><strong><code>secret_key</code> por defecto</strong> (<code>SW2YcwTIb9zpOOhoPsMm</code>) cifra los <code>secureJsonData</code> de cualquier Grafana igual; ponle el tuyo (<code>[security] secret_key</code>) antes de aprovisionar nada, porque cambiarlo después invalida lo cifrado.</li>
</ul>
<p>Referencias: <a href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-access/configure-authentication/">Configure authentication</a> y <a href="https://grafana.com/docs/grafana/latest/administration/provisioning/">Provisioning</a> en la documentación de Grafana.</p>
<h2 id="ver-también">Ver también</h2>
<ul>
<li><a href="/es/posts/proxmox/keycloak-openid-connect/">Proxmox con Keycloak (OpenID)</a> — el mismo patrón de SSO, con Keycloak como proveedor.</li>
</ul>
]]></content:encoded></item></channel></rss>