Files
nomad/lib/auth/oidc/server.go
James Rasell 872bb4f2fe lib: add OIDC provider cache and callback server.
The OIDC provider cache is used by the RPC handler as the OIDC
implementation keeps long lived processes running. These process
include connections to the remote OIDC provider.

The Callback server is used by the CLI and starts when the login
command is triggered. This callback server includes success HTML
which is displayed when the user successfully logs into the remote
OIDC provider.
2023-01-13 13:14:50 +00:00

245 lines
12 KiB
Go

package oidc
import (
"fmt"
"net"
"net/http"
"github.com/hashicorp/cap/oidc"
"github.com/hashicorp/nomad/api"
)
// CallbackServer is started with NewCallbackServer and creates an HTTP
// server for handling loopback OIDC auth redirects.
type CallbackServer struct {
ln net.Listener
url string
clientNonce string
errCh chan error
successCh chan *api.ACLOIDCCompleteAuthRequest
}
// NewCallbackServer creates and starts a new local HTTP server for
// OIDC authentication to redirect to. This is used to capture the
// necessary information to complete the authentication.
func NewCallbackServer(addr string) (*CallbackServer, error) {
// Generate our nonce
nonce, err := oidc.NewID()
if err != nil {
return nil, err
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
// Initialize our callback server
srv := &CallbackServer{
url: fmt.Sprintf("http://%s/oidc/callback", ln.Addr().String()),
ln: ln,
clientNonce: nonce,
errCh: make(chan error, 5),
successCh: make(chan *api.ACLOIDCCompleteAuthRequest, 5),
}
// Register our HTTP route and start the server
mux := http.NewServeMux()
mux.Handle("/oidc/callback", srv)
go func() {
httpServer := &http.Server{Handler: mux}
if err := httpServer.Serve(ln); err != nil {
srv.errCh <- err
}
}()
return srv, nil
}
// Close cleans up and shuts down the server. On close, errors may be
// sent to ErrorCh and should be ignored.
func (s *CallbackServer) Close() error { return s.ln.Close() }
// RedirectURI is the redirect URI that should be provided for the auth.
func (s *CallbackServer) RedirectURI() string { return s.url }
// Nonce returns a generated nonce that can be used for the request.
func (s *CallbackServer) Nonce() string { return s.clientNonce }
// ErrorCh returns a channel where any errors are sent. Errors may be
// sent after Close and should be disregarded.
func (s *CallbackServer) ErrorCh() <-chan error { return s.errCh }
// SuccessCh returns a channel that gets sent a partially completed
// request to complete the OIDC auth with the Waypoint server.
func (s *CallbackServer) SuccessCh() <-chan *api.ACLOIDCCompleteAuthRequest { return s.successCh }
// ServeHTTP implements http.Handler and handles the callback request. This
// isn't usually used directly; use the server address instead.
func (s *CallbackServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
q := req.URL.Query()
// Build our result
result := &api.ACLOIDCCompleteAuthRequest{
State: q.Get("state"),
ClientNonce: s.clientNonce,
Code: q.Get("code"),
}
// Send our result. We don't block here because the channel should be
// buffered, and otherwise we're done.
select {
case s.successCh <- result:
default:
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(serverSuccessHTMLResponse))
}
// serverSuccessHTMLResponse is the HTML response the OIDC callback server uses
// when the user has successfully logged in via the OIDC provider.
const serverSuccessHTMLResponse = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OIDC Authentication Succeeded</title>
<style>
body {
font-size: 14px;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif;
}
hr {
border-color: #fdfdfe;
margin: 24px 0;
}
.container {
display: flex;
justify-content: center;
align-items: center;
height: 70vh;
}
svg.logo {
display: block;
margin: 0 0 20px;
}
.message {
display: flex;
min-width: 40vw;
background: #f0f5ff;
border: 1px solid #1563ff;
margin-bottom: 12px;
padding: 12px 16px 16px 12px;
position: relative;
border-radius: 2px;
font-size: 14px;
color: #0a2d74;
}
.message-content {
margin-left: 4px;
}
.message #checkbox {
fill: #1563ff;
}
.message .message-title {
font-size: 16px;
font-weight: 700;
line-height: 1.25;
}
.message .message-body {
border: 0;
margin-top: 4px;
}
.message p {
font-size: 12px;
margin: 0;
padding: 0;
}
a {
display: block;
margin: 8px 0;
color: #1563ff;
text-decoration: none;
font-weight: 600;
}
a:hover {
color: black;
}
a svg {
fill: currentcolor;
}
.icon {
align-items: center;
display: inline-flex;
justify-content: center;
height: 21px;
width: 21px;
vertical-align: middle;
}
h1 {
font-size: 17.5px;
font-weight: 700;
margin-bottom: 0;
}
h1 + p {
margin: 8px 0 16px 0;
}
</style>
</head>
<body translate="no" >
<div class="container">
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="20 0 342 107" id="LOGOS" height="50">
<defs><style>.cls-1{fill:#00ca8e;}</style></defs>
<path d="M125,61.42V92h-7.44V51.68h10.16L143,82.29V51.68h7.44V92H140.28Z"></path>
<path d="M168.58,92.58c-10.1,0-12.82-5.57-12.82-11.62V73.52c0-6,2.72-11.61,12.82-11.61s12.83,5.56,12.83,11.61V81C181.41,87,178.69,92.58,168.58,92.58Zm0-24.38c-3.93,0-5.44,1.75-5.44,5.08V81.2c0,3.33,1.51,5.09,5.44,5.09S174,84.53,174,81.2V73.28C174,70,172.52,68.2,168.58,68.2Z"></path>
<path d="M203.85,92V71.4c0-1.57-.67-2.36-2.36-2.36s-5,1.09-7.68,2.48V92h-7.38V62.51h5.62l.73,2.48a29.59,29.59,0,0,1,11.8-3.08c2.84,0,4.59,1.15,5.56,3.14A29,29,0,0,1,222,61.91c4.9,0,6.65,3.44,6.65,8.71V92h-7.38V71.4c0-1.57-.66-2.36-2.36-2.36a19.55,19.55,0,0,0-7.68,2.48V92Z"></path>
<path d="M256.54,92h-6.05l-.55-2a16.15,16.15,0,0,1-8.77,2.6c-5.38,0-7.68-3.69-7.68-8.77,0-6,2.6-8.29,8.59-8.29h7.08V72.43c0-3.26-.91-4.41-5.63-4.41a41.55,41.55,0,0,0-8.17.9l-.9-5.62a38.26,38.26,0,0,1,10.1-1.39c9.25,0,12,3.26,12,10.64Zm-7.38-11.13h-5.45c-2.42,0-3.08.67-3.08,2.91,0,2,.66,3,3,3a11.69,11.69,0,0,0,5.57-1.51Z"></path>
<path d="M261.07,72.31c0-6.53,2.9-10.4,9.74-10.4a41.09,41.09,0,0,1,7.87.84V50.47l7.38-1V92h-5.87l-.73-2.48a15.46,15.46,0,0,1-9.31,3.09c-5.93,0-9.08-3.51-9.08-10.23ZM278.68,69a33.06,33.06,0,0,0-6.54-.78c-2.66,0-3.69,1.27-3.69,3.93V82.54c0,2.42.91,3.75,3.63,3.75a10.43,10.43,0,0,0,6.6-2.67Z"></path>
<path class="cls-1" d="M61.14,38.19,32,55V88.63l29.12,16.81L90.26,88.63V55Zm13,37-7.76,4.48L57,74.54V85.26l-8.81,5.59V68.45l7-4.28,9.7,5.11V58.34l9.25-5.56Z"></path>
<path d="M125.42,41.81V36.15h-5.17v5.66h-2.64V28.22h2.64v5.7h5.17v-5.7h2.65V41.81Zm12.33,0h-2.1l-.19-.67a5.69,5.69,0,0,1-3,.87c-1.86,0-2.66-1.23-2.66-2.92,0-2,.9-2.76,3-2.76h2.45v-1c0-1.09-.31-1.47-1.95-1.47a14.94,14.94,0,0,0-2.83.3l-.31-1.87a13.9,13.9,0,0,1,3.5-.46c3.2,0,4.15,1.08,4.15,3.54Zm-2.56-3.7H133.3c-.83,0-1.06.22-1.06,1s.23,1,1,1a4,4,0,0,0,1.93-.51ZM143.1,42a12.59,12.59,0,0,1-3.52-.56l.36-1.88a11.5,11.5,0,0,0,3,.43c1.13,0,1.3-.24,1.3-1s-.13-.9-1.78-1.29c-2.5-.58-2.79-1.18-2.79-3.08s.9-2.83,3.81-2.83a14,14,0,0,1,3.06.34l-.25,2a18.71,18.71,0,0,0-2.81-.28c-1.11,0-1.3.24-1.3.84,0,.79.07.85,1.45,1.19,2.85.73,3.12,1.09,3.12,3.1S146.18,42,143.1,42Zm11.71-.2V35c0-.53-.23-.79-.82-.79a7.12,7.12,0,0,0-2.66.83v6.8h-2.56V28l2.56.38v4.34a9.34,9.34,0,0,1,3.73-.94c1.7,0,2.31,1.14,2.31,2.89v7.11Zm4.7-11.19v-2.4h2.56v2.4Zm0,11.19V32h2.56v9.8Zm4.6-9.72c0-2.46,1.49-3.88,5-3.88a16.47,16.47,0,0,1,3.79.44l-.29,2.19a21.49,21.49,0,0,0-3.42-.34c-1.82,0-2.41.6-2.41,2v5.15c0,1.43.59,2,2.41,2a21.57,21.57,0,0,0,3.42-.35l.29,2.2a16.47,16.47,0,0,1-3.79.44c-3.48,0-5-1.43-5-3.88ZM178.52,42c-3.5,0-4.44-1.85-4.44-3.86V35.67c0-2,.94-3.86,4.44-3.86S183,33.66,183,35.67v2.48C183,40.16,182,42,178.52,42Zm0-8.11c-1.36,0-1.89.58-1.89,1.69v2.64c0,1.1.53,1.69,1.89,1.69s1.89-.59,1.89-1.69V35.59C180.41,34.48,179.88,33.9,178.52,33.9Zm11.64.16a20.53,20.53,0,0,0-2.7,1.43v6.32H184.9V32h2.16l.17,1.08a11.54,11.54,0,0,1,2.68-1.28Zm10.22,4.49c0,2.17-1,3.46-3.37,3.46a14.85,14.85,0,0,1-2.73-.28v4l-2.55.38V32h2l.25.82a5.54,5.54,0,0,1,3.23-1c2,0,3.14,1.16,3.14,3.4Zm-6.1,1.11a12,12,0,0,0,2.27.26c.92,0,1.28-.43,1.28-1.31V35.15c0-.81-.32-1.25-1.26-1.25a3.68,3.68,0,0,0-2.29.89Z"></path>
</svg>
<div class="message">
<svg id="checkbox" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<path fill-rule="evenodd" d="M8 16A8 8 0 108 0a8 8 0 000 16zm.93-9.412l-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM8 5.5a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
</svg>
<div class="message-content">
<div class="message-title">
Signed in via your OIDC provider
</div>
<p class="message-body">
You can now close this window and start using Nomad.
</p>
</div>
</div>
<hr />
<h1>Not sure how to get started?</h1>
<p class="learn">
Check out beginner and advanced guides on HashiCorp Nomad at the HashiCorp Learn site or read more in the official documentation.
</p>
<a href="https://developer.hashicorp.com/nomad/tutorials" rel="noreferrer noopener">
<span class="icon">
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M8.338 2.255a.79.79 0 0 0-.645 0L.657 5.378c-.363.162-.534.538-.534.875 0 .337.171.713.534.875l1.436.637c-.332.495-.638 1.18-.744 2.106a.887.887 0 0 0-.26 1.559c.02.081.03.215.013.392-.02.205-.074.43-.162.636-.186.431-.45.64-.741.64v.98c.651 0 1.108-.365 1.403-.797l.06.073c.32.372.826.763 1.455.763v-.98c-.215 0-.474-.145-.71-.42-.111-.13-.2-.27-.259-.393a1.014 1.014 0 0 1-.06-.155c-.01-.036-.013-.055-.013-.058h-.022a2.544 2.544 0 0 0 .031-.641.886.886 0 0 0-.006-1.51c.1-.868.398-1.477.699-1.891l.332.147-.023.746v2.228c0 .115.04.22.105.304.124.276.343.5.587.677.297.217.675.396 1.097.54.846.288 1.943.456 3.127.456 1.185 0 2.281-.168 3.128-.456.422-.144.8-.323 1.097-.54.244-.177.462-.401.586-.677a.488.488 0 0 0 .106-.304V8.218l2.455-1.09c.363-.162.534-.538.534-.875 0-.337-.17-.713-.534-.875L8.338 2.255zm-.34 2.955L3.64 7.38l4.375 1.942 6.912-3.069-6.912-3.07-6.912 3.07 1.665.74 4.901-2.44.328.657zM14.307 1H12.5a.5.5 0 1 1 0-1h3a.499.499 0 0 1 .5.65V3.5a.5.5 0 1 1-1 0V1.72l-1.793 1.774a.5.5 0 0 1-.713-.701L14.307 1zm-2.368 7.653v2.383a.436.436 0 0 0-.007.021c-.017.063-.084.178-.282.322-.193.14-.473.28-.836.404-.724.247-1.71.404-2.812.404-1.1 0-2.087-.157-2.811-.404a3.188 3.188 0 0 1-.836-.404c-.198-.144-.265-.26-.282-.322a.437.437 0 0 0-.007-.02V8.983l.01-.338 3.617 1.605a.791.791 0 0 0 .645 0l3.6-1.598z" fill-rule="evenodd"></path>
</svg>
</span>
Get started with Nomad
</a>
<a href="https://developer.hashicorp.com/nomad/docs" rel="noreferrer noopener">
<span class="icon">
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M13.307 1H11.5a.5.5 0 1 1 0-1h3a.499.499 0 0 1 .5.65V3.5a.5.5 0 1 1-1 0V1.72l-1.793 1.774a.5.5 0 0 1-.713-.701L13.307 1zM12 14V8a.5.5 0 1 1 1 0v6.5a.5.5 0 0 1-.5.5H.563a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 .5-.5H8a.5.5 0 0 1 0 1H1v12h11zM4 6a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1H4zm0 2.5a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1H4zM4 11a.5.5 0 1 1 0-1h5a.5.5 0 1 1 0 1H4z"/>
</svg>
</span>
View the official Nomad documentation
</a>
</div>
</div>
</body>
</html>
`