Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Serverless IdP for Small Team: Himari

Serverless IdP for Small Team: Himari

Sorah Fukumori

May 11, 2023

More Decks by Sorah Fukumori

Other Decks in Technology


  1. 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!
  2. 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!
  3. 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
  4. 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
  5. 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!
  6. 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…
  7. 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
  8. 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!
  9. 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…
  10. 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.
  11. 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 ), )
  12. 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', )
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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~