$30 off During Our Annual Pro Sale. View Details »

Serverless IdP for Small Team: Himari

Serverless IdP for Small Team: Himari

Sorah Fukumori

May 11, 2023
Tweet

More Decks by Sorah Fukumori

Other Decks in Technology

Transcript

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

    View Slide

  2. 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!

    View Slide

  3. 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!

    View Slide

  4. Agenda
    • Introduction of https://github.com/sorah/himari

    • Motivation

    • Solution

    • Application in RubyKaigi

    • How it works

    • Demo

    View Slide

  5. 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

    View Slide

  6. 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

    View Slide

  7. 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!

    View Slide

  8. 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…

    View Slide

  9. 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

    View Slide

  10. 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!

    View Slide

  11. 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…

    View Slide

  12. 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.

    View Slide

  13. 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
    ),
    )

    View Slide

  14. 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',
    )

    View Slide

  15. 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

    View Slide

  16. 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

    View Slide

  17. 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

    View Slide

  18. 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

    View Slide

  19. 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

    View Slide

  20. Demo

    View Slide

  21. 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~

    View Slide

  22. Conclusion
    • https://github.com/sorah/himari is a Swiss Army Knife OIDC provider

    • Omniauth → do something → Downstream Apps

    View Slide