websspi

package module
v1.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 15, 2021 License: MIT Imports: 2 Imported by: 0

README

websspi

GoDoc Build Status Coverage Status Go Report Card

websspi is an HTTP middleware for Golang that uses Kerberos/NTLM for single sign-on (SSO) authentication of browser based clients in a Windows environment.

It performs authentication of HTTP requests without the need to create or use keytab files.

The middleware implements the scheme defined by RFC4559 (SPNEGO-based HTTP Authentication in Microsoft Windows) to exchange security tokens via HTTP headers and uses SSPI (Security Support Provider Interface) to authenticate HTTP requests.

How to use

The examples directory contains a simple web server that demonstrates how to use the package. Before trying it, you need to prepare your environment:

  1. Create a separate user account in active directory, under which the web server process will be running (eg. user under the domain.local domain)

  2. Create a service principal name for the host with class HTTP:

    • Start Command prompt or PowerShell as domain administrator

    • Run the command below, replacing host.domain.local with the fully qualified domain name of the server where the web application will be running, and domain\user with the name of the account created in step 1.:

      setspn -A HTTP/host.domain.local domain\user
      
  3. Start the web server app under the account created in step 1.

  4. If you are using Chrome, Edge or Internet Explorer, add the URL of the web app to the Local intranet sites (Internet Options -> Security -> Local intranet -> Sites)

  5. Start Chrome, Edge or Internet Explorer and navigate to the URL of the web app (eg. http://host.domain.local:9000)

  6. The web app should greet you with the name of your AD account without asking you to login. In case it doesn't, make sure that:

    • You are not running the web browser on the same server where the web app is running. You should be running the web browser on a domain joined computer (client) that is different from the server. If you do run the web browser at the same server SSPI package will fallback to NTLM protocol and Kerberos will not be used.
    • There is only one HTTP/... SPN for the host
    • The SPN contains only the hostname, without the port
    • You have added the URL of the web app to the Local intranet zone
    • The clocks of the server and client should not differ with more than 5 minutes
    • Integrated Windows Authentication should be enabled in Internet Explorer (under Advanced settings)

Security requirements

  • SPNEGO over HTTP provides no facilities for protection of the authroization data contained in HTTP headers (the Authorization and WWW-Authenticate headers), which means that the web server MUST enforce use of HTTPS to provide confidentiality for the data in those headers!

Documentation

Index

Constants

View Source
const (
	SEC_E_OK = SECURITY_STATUS(0)

	SEC_E_INCOMPLETE_MESSAGE          = SECURITY_STATUS(0x80090318)
	SEC_E_INSUFFICIENT_MEMORY         = SECURITY_STATUS(0x80090300)
	SEC_E_INTERNAL_ERROR              = SECURITY_STATUS(0x80090304)
	SEC_E_INVALID_HANDLE              = SECURITY_STATUS(0x80090301)
	SEC_E_INVALID_TOKEN               = SECURITY_STATUS(0x80090308)
	SEC_E_LOGON_DENIED                = SECURITY_STATUS(0x8009030C)
	SEC_E_NO_AUTHENTICATING_AUTHORITY = SECURITY_STATUS(0x80090311)
	SEC_E_NO_CREDENTIALS              = SECURITY_STATUS(0x8009030E)
	SEC_E_UNSUPPORTED_FUNCTION        = SECURITY_STATUS(0x80090302)
	SEC_I_COMPLETE_AND_CONTINUE       = SECURITY_STATUS(0x00090314)
	SEC_I_COMPLETE_NEEDED             = SECURITY_STATUS(0x00090313)
	SEC_I_CONTINUE_NEEDED             = SECURITY_STATUS(0x00090312)
	SEC_E_NOT_OWNER                   = SECURITY_STATUS(0x80090306)
	SEC_E_SECPKG_NOT_FOUND            = SECURITY_STATUS(0x80090305)
	SEC_E_UNKNOWN_CREDENTIALS         = SECURITY_STATUS(0x8009030D)

	NEGOSSP_NAME         = "Negotiate"
	SECPKG_CRED_INBOUND  = 1
	SECURITY_NATIVE_DREP = 16

	ASC_REQ_DELEGATE        = 1
	ASC_REQ_MUTUAL_AUTH     = 2
	ASC_REQ_REPLAY_DETECT   = 4
	ASC_REQ_SEQUENCE_DETECT = 8
	ASC_REQ_CONFIDENTIALITY = 16
	ASC_REQ_USE_SESSION_KEY = 32
	ASC_REQ_ALLOCATE_MEMORY = 256
	ASC_REQ_USE_DCE_STYLE   = 512
	ASC_REQ_DATAGRAM        = 1024
	ASC_REQ_CONNECTION      = 2048
	ASC_REQ_EXTENDED_ERROR  = 32768
	ASC_REQ_STREAM          = 65536
	ASC_REQ_INTEGRITY       = 131072

	SECPKG_ATTR_SIZES            = 0
	SECPKG_ATTR_NAMES            = 1
	SECPKG_ATTR_LIFESPAN         = 2
	SECPKG_ATTR_DCE_INFO         = 3
	SECPKG_ATTR_STREAM_SIZES     = 4
	SECPKG_ATTR_KEY_INFO         = 5
	SECPKG_ATTR_AUTHORITY        = 6
	SECPKG_ATTR_PROTO_INFO       = 7
	SECPKG_ATTR_PASSWORD_EXPIRY  = 8
	SECPKG_ATTR_SESSION_KEY      = 9
	SECPKG_ATTR_PACKAGE_INFO     = 10
	SECPKG_ATTR_USER_FLAGS       = 11
	SECPKG_ATTR_NEGOTIATION_INFO = 12
	SECPKG_ATTR_NATIVE_NAMES     = 13
	SECPKG_ATTR_FLAGS            = 14
	SECPKG_ATTR_ACCESS_TOKEN     = 18

	SECBUFFER_VERSION = 0
	SECBUFFER_TOKEN   = 2
)
View Source
const (
	NERR_Success       = 0x0
	NERR_InternalError = 0x85C
	NERR_UserNotFound  = 0x8AD

	ERROR_ACCESS_DENIED     = 0x5
	ERROR_BAD_NETPATH       = 0x35
	ERROR_INVALID_LEVEL     = 0x7C
	ERROR_INVALID_NAME      = 0x7B
	ERROR_MORE_DATA         = 0xEA
	ERROR_NOT_ENOUGH_MEMORY = 0x8

	MAX_PREFERRED_LENGTH  = 0xFFFFFFFF
	MAX_GROUP_NAME_LENGTH = 256

	SE_GROUP_MANDATORY          = 0x1
	SE_GROUP_ENABLED_BY_DEFAULT = 0x2
	SE_GROUP_ENABLED            = 0x4
	SE_GROUP_OWNER              = 0x8
	SE_GROUP_USE_FOR_DENY_ONLY  = 0x10
	SE_GROUP_INTEGRITY          = 0x20
	SE_GROUP_INTEGRITY_ENABLED  = 0x40
	SE_GROUP_LOGON_ID           = 0xC0000000
	SE_GROUP_RESOURCE           = 0x20000000
)

Variables

View Source
var (
	UserInfoKey = contextKey("UserInfo")
)

Functions

func UTF16PtrToString

func UTF16PtrToString(ptr *uint16, maxLen int) (s string)

UTF16PtrToString converts a pointer to a UTF-16 sequence to a string. The UTF-16 sequence must null terminated or shorter than maxLen.

If the UTF-16 sequence is longer than maxlen, an empty string is returned.

Types

type API

type API interface {
	AcquireCredentialsHandle(
		principal *uint16,
		_package *uint16,
		credentialUse uint32,
		logonID *LUID,
		authData *byte,
		getKeyFn uintptr,
		getKeyArgument uintptr,
		credHandle *CredHandle,
		expiry *syscall.Filetime,
	) SECURITY_STATUS
	AcceptSecurityContext(
		credential *CredHandle,
		context *CtxtHandle,
		input *SecBufferDesc,
		contextReq uint32,
		targDataRep uint32,
		newContext *CtxtHandle,
		output *SecBufferDesc,
		contextAttr *uint32,
		expiry *syscall.Filetime,
	) SECURITY_STATUS
	QueryContextAttributes(context *CtxtHandle, attribute uint32, buffer *byte) SECURITY_STATUS
	DeleteSecurityContext(context *CtxtHandle) SECURITY_STATUS
	FreeContextBuffer(buffer *byte) SECURITY_STATUS
	FreeCredentialsHandle(handle *CredHandle) SECURITY_STATUS
	NetUserGetGroups(
		serverName *uint16,
		userName *uint16,
		level uint32,
		buf **byte,
		prefmaxlen uint32,
		entriesread *uint32,
		totalentries *uint32,
	) (neterr error)
	NetApiBufferFree(buf *byte) (neterr error)

	GetTokenInformation(t syscall.Token, infoClass uint32, info *byte, infoLen uint32, returnedLen *uint32) (err error)
}

The API interface describes the Win32 functions used in this package and its primary purpose is to allow replacing them with stub functions in unit tests.

type Authenticator

type Authenticator struct {
	Config Config
	// contains filtered or unexported fields
}

The Authenticator type provides middleware methods for authentication of http requests. A single authenticator object can be shared by concurrent goroutines.

func New

func New(config *Config) (*Authenticator, error)

New creates a new Authenticator object with the given configuration options.

func (*Authenticator) AcceptOrContinue

func (a *Authenticator) AcceptOrContinue(context *CtxtHandle, authData []byte) (newCtx *CtxtHandle, out []byte, exp *time.Time, err error)

AcceptOrContinue tries to validate the auth-data token by calling the AcceptSecurityContext function and returns and error if validation failed or continuation of the negotiation is needed. No error is returned if the token was validated (user was authenticated).

func (*Authenticator) AppendAuthenticateHeader

func (a *Authenticator) AppendAuthenticateHeader(w http.ResponseWriter, data string)

AppendAuthenticateHeader populates WWW-Authenticate header, indicating to client that authentication is required and returns a 401 (Unauthorized) response code. The data parameter can be empty for the first 401 response from the server. For subsequent 401 responses the data parameter should contain the gssapi-data, which is required for continuation of the negotiation.

func (*Authenticator) Authenticate

func (a *Authenticator) Authenticate(r *http.Request, w http.ResponseWriter) (userInfo *UserInfo, outToken string, err error)

Authenticate tries to authenticate the HTTP request and returns nil if authentication was successful. Returns error and data for continuation if authentication was not successful.

func (*Authenticator) Free

func (a *Authenticator) Free() error

Free method should be called before shutting down the server to let it release allocated Win32 resources

func (*Authenticator) GetAuthData

func (a *Authenticator) GetAuthData(r *http.Request, w http.ResponseWriter) (authData []byte, err error)

GetAuthData parses the "Authorization" header received from the client, extracts the auth-data token (input token) and decodes it to []byte

func (*Authenticator) GetCtxHandle

func (a *Authenticator) GetCtxHandle(r *http.Request) (*CtxtHandle, error)

GetCtxHandle retrieves the context handle for this client from request's cookies

func (*Authenticator) GetFlags

func (a *Authenticator) GetFlags(context *CtxtHandle) (uint32, error)

GetFlags returns the negotiated context flags

func (*Authenticator) GetGroups added in v1.1.0

func (a *Authenticator) GetGroups(context *CtxtHandle) (groups []string, err error)

GetGroups returns the groups assosiated with the specified security context

func (*Authenticator) GetUserGroups

func (a *Authenticator) GetUserGroups(userName string) (groups []string, err error)

GetUserGroups returns the groups the user is a member of

func (*Authenticator) GetUserInfo

func (a *Authenticator) GetUserInfo(context *CtxtHandle) (*UserInfo, error)

GetUserInfo returns a structure containing the name of the user associated with the specified security context and the groups to which they are a member of (if Config.EnumerateGroups) is enabled

func (*Authenticator) GetUsername

func (a *Authenticator) GetUsername(context *CtxtHandle) (username string, err error)

GetUsername returns the name of the user associated with the specified security context

func (*Authenticator) PrepareCredentials

func (a *Authenticator) PrepareCredentials(principal string) error

PrepareCredentials method acquires a credentials handle for the specified principal for use during the live of the application. On success stores the handle in the serverCred field and its expiry time in the credExpiry field. This method must be called once - when the application is starting or when the first request from a client is received.

func (*Authenticator) ReleaseCtxHandle

func (a *Authenticator) ReleaseCtxHandle(handle *CtxtHandle) error

ReleaseCtxHandle deletes a context handle and removes it from the internal list (ctxList)

func (*Authenticator) Return401

func (a *Authenticator) Return401(w http.ResponseWriter, data string)

Return401 populates WWW-Authenticate header, indicating to client that authentication is required and returns a 401 (Unauthorized) response code. The data parameter can be empty for the first 401 response from the server. For subsequent 401 responses the data parameter should contain the gssapi-data, which is required for continuation of the negotiation.

func (*Authenticator) SetCtxHandle

func (a *Authenticator) SetCtxHandle(r *http.Request, w http.ResponseWriter, newContext *CtxtHandle) error

SetCtxHandle stores the context handle for this client to cookie of response

func (*Authenticator) StoreCtxHandle

func (a *Authenticator) StoreCtxHandle(handle *CtxtHandle)

StoreCtxHandle stores the specified context to the internal list (ctxList)

func (*Authenticator) WithAuth

func (a *Authenticator) WithAuth(next http.Handler) http.Handler

WithAuth authenticates the request. On successful authentication the request is passed down to the next http handler. The next handler can access information about the authenticated user via the GetUserName method. If authentication was not successful, the server returns 401 response code with a WWW-Authenticate, indicating that authentication is required.

type Config

type Config struct {
	KrbPrincipal    string // Name of Kerberos principle used by the service (optional).
	AuthUserKey     string // Key of header to fill with authenticated username, eg. "X-Authenticated-User" or "REMOTE_USER" (optional).
	EnumerateGroups bool   // If true, groups the user is a member of are enumerated and stored in request context (default false)
	ServerName      string // Specifies the DNS or NetBIOS name of the remote server which to query about user groups. Use an empty value to query the groups granted on a real login. Ignored if EnumerateGroups is false.
	ResolveLinked   bool   // Resolve a linked token.
	// contains filtered or unexported fields
}

The Config object determines the behaviour of the Authenticator.

To resolve group membership of authenticated principals, set EnumerateGroups to true. Currently there are two options to resolve group membership - both return different results. To resolve the "static" local or AD group membership, additionally set "ServerName" to a Windows server or Active Directory.

func NewConfig

func NewConfig() *Config

NewConfig creates a configuration object with default values.

func (*Config) Validate

func (c *Config) Validate() error

Validate makes basic validation of configuration to make sure that important and required fields have been set with values in expected format.

type CredHandle

type CredHandle struct {
	Lower uintptr
	Upper uintptr
}

type CtxtHandle

type CtxtHandle struct {
	Lower uintptr
	Upper uintptr
}

type GroupUsersInfo0

type GroupUsersInfo0 struct {
	Grui0_name *uint16
}

type GroupUsersInfo1

type GroupUsersInfo1 struct {
	Grui1_name       *uint16
	Grui1_attributes uint32
}

type LUID

type LUID struct {
	LowPart  uint32
	HighPart int32
}

type SECURITY_STATUS

type SECURITY_STATUS syscall.Errno

type SecBuffer

type SecBuffer struct {
	BufferSize uint32
	BufferType uint32
	Buffer     *byte
}

type SecBufferDesc

type SecBufferDesc struct {
	Version      uint32
	BuffersCount uint32
	Buffers      *SecBuffer
}

type SecPkgContext_AccessToken added in v1.1.0

type SecPkgContext_AccessToken struct {
	AccessToken uintptr
}

type SecPkgContext_Flags

type SecPkgContext_Flags struct {
	Flags uint32
}

type SecPkgContext_Names

type SecPkgContext_Names struct {
	UserName *uint16
}

type TokenGroups added in v1.1.0

type TokenGroups struct {
	GroupCount uint32                   // DWORD
	Groups     syscall.SIDAndAttributes // *SIDAndAttributes[]
}

type UserInfo

type UserInfo struct {
	Username string   // Name of user, usually in the form DOMAIN\User
	Groups   []string // The global groups the user is a member of
}

UserInfo represents an authenticated user.

type Win32

type Win32 struct{}

Win32 implements the API interface by calling the relevant system functions from secur32.dll and netapi32.dll

func (*Win32) AcceptSecurityContext

func (w *Win32) AcceptSecurityContext(
	credential *CredHandle,
	context *CtxtHandle,
	input *SecBufferDesc,
	contextReq uint32,
	targDataRep uint32,
	newContext *CtxtHandle,
	output *SecBufferDesc,
	contextAttr *uint32,
	expiry *syscall.Filetime,
) SECURITY_STATUS

func (*Win32) AcquireCredentialsHandle

func (w *Win32) AcquireCredentialsHandle(
	principal *uint16,
	_package *uint16,
	credentialUse uint32,
	logonId *LUID,
	authData *byte,
	getKeyFn uintptr,
	getKeyArgument uintptr,
	credHandle *CredHandle,
	expiry *syscall.Filetime,
) SECURITY_STATUS

func (*Win32) DeleteSecurityContext

func (w *Win32) DeleteSecurityContext(context *CtxtHandle) SECURITY_STATUS

func (*Win32) FreeContextBuffer

func (w *Win32) FreeContextBuffer(buffer *byte) SECURITY_STATUS

func (*Win32) FreeCredentialsHandle

func (w *Win32) FreeCredentialsHandle(handle *CredHandle) SECURITY_STATUS

func (*Win32) GetTokenInformation added in v1.1.2

func (w *Win32) GetTokenInformation(t syscall.Token, infoClass uint32, info *byte, infoLen uint32, returnedLen *uint32) (err error)

func (*Win32) NetApiBufferFree

func (w *Win32) NetApiBufferFree(buf *byte) (neterr error)

func (*Win32) NetUserGetGroups

func (w *Win32) NetUserGetGroups(
	serverName *uint16,
	userName *uint16,
	level uint32,
	buf **byte,
	prefmaxlen uint32,
	entriesread *uint32,
	totalentries *uint32,
) (neterr error)

func (*Win32) QueryContextAttributes

func (w *Win32) QueryContextAttributes(
	context *CtxtHandle,
	attribute uint32,
	buffer *byte,
) SECURITY_STATUS

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL