<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Upstream | 2i2c</title><link>https://deploy-preview-631--2i2c-org.netlify.app/tag/upstream/</link><atom:link href="https://deploy-preview-631--2i2c-org.netlify.app/tag/upstream/index.xml" rel="self" type="application/rss+xml"/><description>Upstream</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Wed, 26 Aug 2026 00:00:00 +0000</lastBuildDate><image><url>https://deploy-preview-631--2i2c-org.netlify.app/media/sharing.png</url><title>Upstream</title><link>https://deploy-preview-631--2i2c-org.netlify.app/tag/upstream/</link></image><item><title>Building an authentication helper library for JupyterHub</title><link>https://deploy-preview-631--2i2c-org.netlify.app/blog/buliding-jhub-authz/</link><pubDate>Wed, 26 Aug 2026 00:00:00 +0000</pubDate><guid>https://deploy-preview-631--2i2c-org.netlify.app/blog/buliding-jhub-authz/</guid><description>&lt;p>2i2c have been working with the &lt;a href="https://deploy-preview-631--2i2c-org.netlify.app/collaborators/utoronto/" >University of Toronto&lt;/a> to add support for logging in to a JupyterHub using Canvas as an Identity Provider. JupyterHub implements support for &lt;em>authenticating&lt;/em> users (figuring out who there are) and &lt;em>authorizing&lt;/em> users (figuring out whether they are permitted to log into the hub) using an &lt;code>Authenicator&lt;/code> interface. Via the &lt;code>oauthenticator&lt;/code> package, JupyterHub has support for many kinds of OAuth2-based Identity Providers such as Google or GitHub, in addition to providing a &lt;em>generic&lt;/em> authenticator &lt;code>GenericOAuthenticator&lt;/code> that can be used with any OAuth2 implementation.&lt;/p>
&lt;figure id="figure-diagram-of-an-oauth-flow-during-the-authentication-process-taken-from-the-oauthenticator-documentationhttpsoauthenticatorreadthedocsioenlatest_imagesjupyterhub-oauth-external-flowpng">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Diagram of an OAuth flow during the authentication process, taken from [the oauthenticator documentation](https://oauthenticator.readthedocs.io/en/latest/_images/JupyterHub-OAuth-external-flow.png)." srcset="
/blog/buliding-jhub-authz/oauth-flow_hu0c8a8127bb8c611237d98151170d7a50_76835_3b90dd686be44dc5e5271fe8b6a74d81.webp 400w,
/blog/buliding-jhub-authz/oauth-flow_hu0c8a8127bb8c611237d98151170d7a50_76835_b76b2c9bead95c9862c4df0ce3110d69.webp 760w,
/blog/buliding-jhub-authz/oauth-flow_hu0c8a8127bb8c611237d98151170d7a50_76835_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://deploy-preview-631--2i2c-org.netlify.app/blog/buliding-jhub-authz/oauth-flow_hu0c8a8127bb8c611237d98151170d7a50_76835_3b90dd686be44dc5e5271fe8b6a74d81.webp"
width="760"
height="428"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption>
Diagram of an OAuth flow during the authentication process, taken from &lt;a href="https://oauthenticator.readthedocs.io/en/latest/_images/JupyterHub-OAuth-external-flow.png" target="_blank" rel="noopener" >the oauthenticator documentation&lt;/a>.
&lt;/figcaption>&lt;/figure>
&lt;p>Our friends at UC Berkeley maintain the &lt;code>CanvasOAuthenticator&lt;/code> project, an implementation of a Canvas OAuth2 authenticator for JupyterHub. This project has been in-use by UC Berkeley for over four years, authenticating thousands of students. In the time since &lt;code>CanvasOAuthenticator&lt;/code> was first written (with involvement from 2i2c members), the &lt;code>GenericOAuthenticator&lt;/code> has seen significant improvements, such that it is now possible to implement Canvas authentication without maintaining an entirely dedicated authenticator project.&lt;/p>
&lt;p>After recognizing that it is much easier to maintain a small set of OAuth2 rules (adaptors) for each Identity Provider (like Canvas) rather than full-blown authenticators, 2i2c built &lt;a href="https://2i2c.org/jupyterhub-oauthenticator-authz-helpers/" target="_blank" rel="noopener" >&lt;code>jupyterhub_oauthenticator_authz_helpers&lt;/code>&lt;/a>. This library maintains an implementation of a subset of UC Berkeley&amp;rsquo;s &lt;code>CanvasOAuthenticator&lt;/code> rules for mapping Canvas courses and groups onto JupyterHub groups. With this work, adding Canvas authentication requires only a few additional lines:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">jupyterhub_oauthenticator_authz_helpers.canvas&lt;/span> &lt;span class="n">build_auth_urls&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cfg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">GenericOAuthenticator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">canvas_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;CANVAS-URL&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Configure auths&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">authorize_url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">token_url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">userdata_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">build_auth_urls&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">canvas_url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Scopes that this token will need&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">scope&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">build_auth_urls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">scopes&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Adding support for group population (from Canvas groups and courses) requires a little bit more config:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">jupyterhub_oauthenticator_authz_helpers.canvas&lt;/span> &lt;span class="n">get_user_groups&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">get_course_groups&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">build_auth_urls&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Define a custom key for auth groups&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">auth_state_groups_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;custom-groups&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Define and inject auth state&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">auth_state_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">authenticator&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">auth_state&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">auth_state&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">access_token&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">auth_state&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;access_token&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">auth_state&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">authenticator&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">auth_state_groups_key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Populate groups from Canvas courses, using the scheme defined in get_course_groups&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">*&lt;/span>&lt;span class="k">await&lt;/span> &lt;span class="n">get_course_groups&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">canvas_url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">access_token&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;course_code&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Populate groups from Canvas groups, using the scheme defined in get_user_groups&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">*&lt;/span>&lt;span class="k">await&lt;/span> &lt;span class="n">get_user_groups&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">canvas_url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">access_token&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">auth_state&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">modify_auth_state_hook&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">auth_state_hook&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Request additional scopes for the course / user group fetching&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">scope&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">build_auth_urls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">scopes&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">get_user_groups&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">scopes&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">get_course_groups&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">scopes&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In addition to building out this library, 2i2c hopes to foster collaboration with other users and maintainers to continue to upstream additional OAuth2 adaptors. As part of the funded initiative, 2i2c will spend time conducting gonvernance and community work upstream to establish a &lt;a href="https://github.com/jupyterhub-contrib" target="_blank" rel="noopener" >jupyterhub-contrib&lt;/a> organisation that provides stewardship and maintainance of this work, and future third-party projects.&lt;/p>
&lt;h2 id="acknowledgements">
Acknowledgements
&lt;a class="header-anchor" href="#acknowledgements">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>Thanks to the &lt;a href="https://deploy-preview-631--2i2c-org.netlify.app/collaborators/utoronto/" >University of Toronto&lt;/a> for collaborating with us, and funding this work.&lt;/li>
&lt;li>Thanks to UC Berkeley for the prior work that inspired this project.&lt;/li>
&lt;/ul></description></item><item><title>How regularly upgrading core infrastructure leads to upstream improvements and better infrastructure</title><link>https://deploy-preview-631--2i2c-org.netlify.app/blog/why-upgrade-regularly/</link><pubDate>Fri, 03 Apr 2026 00:00:00 +0000</pubDate><guid>https://deploy-preview-631--2i2c-org.netlify.app/blog/why-upgrade-regularly/</guid><description>&lt;p>Our collaborators at &lt;a href="https://deploy-preview-631--2i2c-org.netlify.app/collaborators/nasa-veda/" >NASA VEDA&lt;/a> recently asked us about the rationale behind policies for upgrading our infrastructure relatively quickly when new versions come out. Here&amp;rsquo;s the explanation that we shared with them, in case it&amp;rsquo;s useful for others as well.&lt;/p>
&lt;p>In this case, the decision was whether to upgrade to Helm 4, and you can find our &lt;a href="https://github.com/2i2c-org/initiatives/issues/4" target="_blank" rel="noopener" >rationale in the &lt;code>/initiatives&lt;/code> repository&lt;/a>. Here&amp;rsquo;s a brief summary from Yuvi:&lt;/p>
&lt;p>Fundamentally, it helps keep moving us and the ecosystem forward, and drive improvements upstream, in both JupyterHub and Helm.&lt;/p>
&lt;p>It has driven these PRs in &lt;a href="https://deploy-preview-631--2i2c-org.netlify.app/collaborators/jupyterhub/" >JupyterHub&lt;/a>:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/jupyterhub/action-k3s-helm/pull/126" target="_blank" rel="noopener" >&lt;i class='fa-brands fa-github'>&lt;/i> jupyterhub/action-k3s-helm#126&lt;/a> (merged)&lt;/li>
&lt;li>&lt;a href="https://github.com/jupyterhub/zero-to-jupyterhub-k8s/pull/3797" target="_blank" rel="noopener" >&lt;i class='fa-brands fa-github'>&lt;/i> jupyterhub/zero-to-jupyterhub-k8s#3797&lt;/a> (validated, but not merged yet)&lt;/li>
&lt;/ul>
&lt;p>It&amp;rsquo;s also driven improvements to helm itself - see this bug report that is being worked on:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/helm/helm/issues/31919" target="_blank" rel="noopener" >&lt;i class='fa-brands fa-github'>&lt;/i> helm/helm#31919&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Upgrading helm versions can break things (and it has for some of our other communities in the past - see &lt;a href="https://github.com/2i2c-org/infrastructure/pull/7886#issuecomment-4031310423" target="_blank" rel="noopener" >this example&lt;/a>). So it&amp;rsquo;s important we do that on a reasonable timeframe and carefully, to avoid disruptions.&lt;/p>
&lt;p>We&amp;rsquo;re also discovering for example that potentially the new &lt;code>nginx-ingress&lt;/code> controller we had to move to has some issues working with older helm versions (ongoing WIP in &lt;a href="https://github.com/2i2c-org/infrastructure/pull/7995%29" target="_blank" rel="noopener" >&lt;i class='fa-brands fa-github'>&lt;/i> 2i2c-org/infrastructure#7995)&lt;/a>. That feels much more tractable because we can now go &amp;lsquo;ok, let us just apply a quick fix now, and wait for the helm 4 rollout, and try again&amp;rsquo; instead of being totally stuck.&lt;/p>
&lt;p>This is similar to the other part of [/our VEDA objective] - rolling out new versions of jupyterhub. If we need to roll out security fixes, it&amp;rsquo;s much easier now because we already did the hard work of being up to date:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/2i2c-org/infrastructure/issues/7996" target="_blank" rel="noopener" >&lt;i class='fa-brands fa-github'>&lt;/i> 2i2c-org/infrastructure#7996&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>This isn&amp;rsquo;t the case quite yet for helm v3, as it&amp;rsquo;s still supported, but it&amp;rsquo;s much better to do this work earlier than wait.&lt;/p>
&lt;p>If you encounter a bug in a popular open source software, often you can just &amp;lsquo;wait&amp;rsquo; for it to be fixed. But this isn&amp;rsquo;t just about time - someone somewhere has to put in the &lt;em>effort&lt;/em> of getting it fixed, filing helpful upstream bug reports, and testing to make sure it works. This is an example of 2i2c continuing to contribute this &lt;em>effort&lt;/em> upstream wherever we can.&lt;/p>
&lt;h2 id="acknowledgements">
Acknowledgements
&lt;a class="header-anchor" href="#acknowledgements">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>Thanks to &lt;a href="https://deploy-preview-631--2i2c-org.netlify.app/collaborators/nasa-veda/" >NASA VEDA&lt;/a> for collaborating deeply with us on infrastructure questions like this.&lt;/li>
&lt;/ul></description></item></channel></rss>