Slide 1

Slide 1 text

Serverless IdP for Small Team Sorah Fukumori https://sorah.jp/

Slide 2

Slide 2 text

Whoami Sorah Fukumori (ͦΒ͸) • https://sorah.jp • https://github.com/sorah • RubyKaigi Organizer: Sponsor Relations, Wi-Fi, Virtual Venue • Sta ff Software Engineer at Cookpad SRE and IT • My recent working hours spent for RubyKaigi as a part of Wi-Fi sponsorship • Infrastructure for the Drink Fridge is built by me~
 • Please Remember: When I appear in the hallway, I’m not busy and happy to chat with Rubyists even I face against my laptop!

Slide 3

Slide 3 text

RubyKaigi 2023 Wi-Fi so far • approx. 1,200 clients • Inbound around 300Mbps • 1/3 of DNS queries use DNS over HTTP/2 • I believe it’s pretty stable this year • Kudos for my Network Ops team members! • https://rubykaigi.org/2023/about/ • We’re running NOC team trivia quiz at Cookpad booth, come visit us!

Slide 4

Slide 4 text

Agenda • Introduction of https://github.com/sorah/himari • Motivation • Solution • Application in RubyKaigi • How it works • Demo

Slide 5

Slide 5 text

Himari: Omniauth to OpenID Connect (OIDC) • https://github.com/sorah/himari - Rack app (using Sinatra) • Federate Omniauth result to OIDC • Authenticate users using Omniauth as a upstream provider • Provide authentication for downstream OIDC relying parties • We can apply a lot of omniauth strategies for OIDC world! • https://dexidp.io/ alternative omniauth-google-oauth2 omniauth-github omniauth-… himari OIDC apps Authorize and generate claims data Provide user authentication

Slide 6

Slide 6 text

Motivation • For single-sign on thingy, most organisations like companies can utilise a full- suite IdPs like Azure AD, Google Workspace, and Okta • But what about small team or personal groups? • Sometimes we want to deploy apps and authorise users using their existing social logins • But some apps don’t support our preferred way or can’t authorise users based on users role easily

Slide 7

Slide 7 text

Motivation: RubyKaigi • I created this tool for RubyKaigi • RubyKaigi organising team is formed by individuals and we prefer not to have additional credentials for the project like using Google Workspace. • But it’s not easy to authorise individual @gmail.com accounts… • Plus, we’d like to keep subscriptions minimum!

Slide 8

Slide 8 text

Motivation: RubyKaigi • Some RubyKaigi tools have started to use Himari and our Himari deployment uses GitHub strategy and GitHub org/team membership to determine role for access control • There’s less convenient and common way to implement access control using individual account, so we need something acts as IdP, but relying on other login method • Not all apps support ACL by Google Groups membership or GitHub Team membership…

Slide 9

Slide 9 text

Solution • What I need is: Use individual-owned accounts, and do some authorization with ID token claims manipulation • So, • Use Omniauth to allow arbitrary strategy for upstream identity • Some simple rule processor for authn/authz - write as a Ruby code • OIDC for downstream apps. • Thanks to nov/openid_connect gem for low-level protocol implementation

Slide 10

Slide 10 text

How it works 1. A downstream app initiates OIDC fl ow against Himari 2. Himari redirects user to Omniauth strategy (/auth/…) 3. Generate ID token claims based on Omniauth auth hash and Claims Rule 4. Authenticate a user by evaluating Authn Rule with the generated claims 5. Authorize a downstream app with the claims and Authz Rule 6. A downstream app receives the ID token and voila!

Slide 11

Slide 11 text

Rule evaluation • So Himari has: Claims Rule, Authentication Rule, and Authorization Rule • Claims Rule determines ID token claims from Omniauth auth hash • Authn Rule determines whether a incoming user can use Himari or not • Authz Rule determines whether the authenticated user a downstream app or not • All rule types use the same evaluation engine.
 Each rule can decide incoming claims/request to allow, explicit deny, skip, or continue. Claims Rule and Authz Rule can update the ID token claims for later use. • Let’s take a look of actual deployment…

Slide 12

Slide 12 text

Example at RubyKaigi • https://github.com/ruby-no-kai/rubykaigi-nw/tree/master/tf/himari • Con fi guration is entirely done using Rack middlewares. • We authenticate team members using GitHub and org/team membership.

Slide 13

Slide 13 text

Example at RubyKaigi • https://github.com/ruby-no-kai/rubykaigi-nw/tree/master/tf/himari • Con fi guration is entirely done using Rack middlewares. use(Himari::Middlewares::Client, name: 'grafana', id: '88445b54-09dd-6790-ecf0-1e7bbfd5e12d', secret_hash: …, # sha384.hexdigest redirect_uris: %w( https://grafana.rubykaigi.net/login/generic_oauth ), )

Slide 14

Slide 14 text

Example at RubyKaigi • https://github.com/ruby-no-kai/rubykaigi-nw/tree/master/tf/himari • There’s a provider API so some con fi g items can be dynamically generated from other sources (e.g. AWS Secrets Manager for signing key) use(Himari::Aws::SecretsmanagerSigningKeyProvider, secret_id: ENV.fetch('HIMARI_SIGNING_KEY_ARN'), group: nil, kid_prefix: 'asm1', )

Slide 15

Slide 15 text

Example at RubyKaigi • https://github.com/ruby-no-kai/rubykaigi-nw/tree/master/tf/himari • Claims rule to initialize claims from github auth hash: use(Himari::Middlewares::ClaimsRule, name: 'github-initialize') do |context, decision| next decision.skip!("provider not in scope") unless context.provider == 'github' decision.initialize_claims!( sub: "github_#{context.auth[:uid]}", name: context.auth[:info][:nickname], preferred_username: context.auth[:info][:nickname], email: context.auth[:info][:email], ) decision.user_data[:provider] = 'github' decision.continue! end

Slide 16

Slide 16 text

Example at RubyKaigi • https://github.com/ruby-no-kai/rubykaigi-nw/tree/master/tf/himari • Store user’s GitHub team memberships in claims: use(Himari::Middlewares::ClaimsRule, name: 'github-oauth-teams') do |context, decision| # (snip) teams_in_scope = [‘ruby-no-kai/rko-infra’, …] teams = user_teams_resp .map { |team| "#{team.fetch('organization').fetch('login')}/#{team.fetch('slug')}" } .select { |login_slug| teams_in_scope.any? { |s| s === login_slug } } next decision.skip!("no teams in scope") if teams.empty? # claims decision.claims[:groups] ||= [] decision.claims[:groups].concat(teams) decision.claims[:groups].uniq! decision.continue! End

Slide 17

Slide 17 text

Example at RubyKaigi • https://github.com/ruby-no-kai/rubykaigi-nw/tree/master/tf/himari • Authenticate user if user belongs to a known GitHub team: use(Himari::Middlewares::AuthenticationRule, name: 'allow-github-with-teams') do |context, decision| next decision.skip!("provider not in scope") unless context.provider == 'github' if context.claims[:groups] && !context.claims[:groups].empty? next decision.allow! end decision.skip!("no available groups") end

Slide 18

Slide 18 text

Example at RubyKaigi • https://github.com/ruby-no-kai/rubykaigi-nw/tree/master/tf/himari • Authorize a downstream app based on groups claims and customize outbound ID token: use(Himari::Middlewares::AuthorizationRule, name: 'grafana') do |context, decision| # …snip… if groups.include?('ruby-no-kai/rk-noc') roles.push('admin') else roles.push('viewer') end decision.claims[:roles] = roles decision.allowed_claims.push(:roles) unless roles.empty? next decision.allow! end decision.skip! end

Slide 19

Slide 19 text

sorah/himari2amc: Access to AWS • https://github.com/sorah/himari2amc • RubyKaigi also uses the above app to access AWS console and API • Himari2amc utilises sts:AssumeRoleWithWebIdentity for federation • But to modify and re-sign ID claims upon role selection, Himari2amc also posses a signing key and openid-con fi guration… • Himari generates a claim for allowed role ARNs using Authz Rule

Slide 20

Slide 20 text

Demo

Slide 21

Slide 21 text

Serverless Support • As explained earlier, RubyKaigi wants to keep subscriptions minimum • So Himari supports Serverless deployment using AWS Lambda • And provides Terraform module for quick deployment • https://github.com/sorah/apigatewayv2_rack for connecting API Gateway to Rack app • There are some alternatives though… • You can utilize this small gem for your own Rack app~

Slide 22

Slide 22 text

Conclusion • https://github.com/sorah/himari is a Swiss Army Knife OIDC provider • Omniauth → do something → Downstream Apps