// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 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", addr), 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 Nomad 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 = ` OIDC Authentication Succeeded
Signed in via your OIDC provider

You can now close this window and start using Nomad.


Not sure how to get started?

Check out beginner and advanced guides on HashiCorp Nomad at the HashiCorp Learn site or read more in the official documentation.

Get started with Nomad View the official Nomad documentation
`