Skip to content

An example of Perfect Empty Starter Project with SPNEGO feature

License

Notifications You must be signed in to change notification settings

PerfectExamples/Perfect-Spnego-Demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Perfect SPNEGO demo 简体中文

Get Involed with Perfect!

Star Perfect On Github Stack Overflow Follow Perfect on Twitter Join the Perfect Slack

Swift 3.0 Platforms OS X | Linux License Apache PerfectlySoft Twitter Slack Status

Perfect Empty Starter Project with SPNEGO feature

This example is based on PerfectTemplate. If you are not familiar PerfectTemplate, please try it first.

Building Notes

This project can be built with Swift 3.0.2 toolchain on both Ubuntu and macOS.

Xcode Build Note

If you would like to use Xcode to build this project, please make sure to pass proper linker flags to the Swift Package Manager:

$ swift package -Xlinker -framework -Xlinker GSS generate-xcodeproj

Linux Build Note

A special library called libkrb5-dev is required to build this project:

$ sudo apt-get install libkrb5-dev

KDC Configuration

Configure the application server's /etc/krb5.conf to your KDC. The following sample configuration shows how to connect your application server to realm KRB5.CA under control of a KDC named nut.krb5.ca:

[realms]
KRB5.CA = {
	kdc = nut.krb5.ca
	admin_server = nut.krb5.ca
}
[domain_realm]
.krb5.ca = KRB5.CA
krb5.ca = KRB5.CA

Prepare Kerberos Keys for Server

Contact to your KDC administrator to assign a keytab file to your application server.

Take example, SUPPOSE ALL HOSTS BELOW REGISTERED ON THE SAME DNS SERVER:

  • KDC server: nut.krb5.ca
  • Application server: apple.krb5.ca
  • Application server type: HTTP

In such a case, KDC administrator shall login on nut.krb5.ca then perform following operation:

kadmin.local: addprinc -randkey HTTP/[email protected]
kadmin.local: ktadd -k /tmp/krb5.keytab HTTP/[email protected]

Then please ship this krb5.keytab file securely and install on your application server apple.krb5.ca and move to folder /etc, then grant sufficient permissions to your swift application to access it.

Building & Running

The following will clone and build an empty starter project with SPNEGO plugin and launch the server on port 8080.

git clone https://github.com/PerfectExamples/Perfect-SPNEGO-Demo.git
cd Perfect-SPNEGO-Demo
swift build
.build/debug/PerfectTemplate

You should see the following output:

[INFO] Starting HTTP server localhost on 0.0.0.0:8080

This means the servers are running and waiting for connections.

Now, you can check http:https://apple.krb5.ca:8080 to see if it works. If you are using SPNEGO compatible browser, such Safari, then a login dialog may pop up and ask for credentials. Or alternatively, you can use a curl command to better understand what will happen:

$ kinit
$ curl -v --negotiate -u : http:https://apple.krb5.ca:8080

You may find that curl would return "unauthorized" unless providing kinit with correct user name / password.

URL Note

To run this demo correctly, please use FQDN (fully qualified domain name) instead of localhost or ip address on both client and server.

Go Through the SPNEGO plugin

The demo source added a few different lines to the PerfectTemplate to verify user identification.

Firstly, it initializes a SPNEGO object before accepting incoming HTTP requests. Secondly, it sets the default response to .unauthorized and instruct the client to negotiate with the server by SPNEGO tokens. If a valid negotiation based64 token was found on the request, the server would try to accept it and figure out who was trying to request the current resource by calling the only method of SPNEGO - let (username, reply_token) = try spnego.accept(base64Token: inputToken)

If username is not nil, it means that the user is valid and you may validate its permission on the current resource link by verifying your own ACL (access control list), otherwise the request shall be rejected.

Please note that an reply token might also be generated and you should send this new token back as a fulfilled response.

import PerfectSPNEGO
import Darwin
// NOTE: Host Name Must Be FQDN and registered with a valid keytab from KDC.
let hostname = "apple.krb5.ca"

// a secured handler
func handler(data: [String:Any]) throws -> RequestHandler {
	return {
		request, response in

		// The spnego requests every secured responses MUST be unauthorized because it's session independent.
    response.status = .unauthorized

    // Client and Server must be synchronized, so return it with a GMT time.
    response.setHeader(.date, value: GMTNow())

    // Instruct the client to negotiate with server.
    response.setHeader(.wwwAuthenticate, value: "Negotiate")
    response.setHeader(.contentType, value: "text/html")

    // if the client has no valid kerberos ticket, reject it.
    guard let auth = request.header(.custom(name: "Authorization")) else {
      response.appendBody(string: "<html><H1>ACCESS DENIED</H1></html>\n")
      response.completed()
      return
    }//end auth

    // extract a spnego token from client
    let negotiate = "Negotiate "
    guard auth.hasPrefix(negotiate) else {
      response.appendBody(string: "<html><H1>INVALID TOKEN FORMAT</H1></html>\n")
      response.completed()
      return
    }//end auth.prefix

    // parse the token from the client request header
    let inputToken = String(auth.characters.dropFirst(negotiate.utf8.count))
    do {

      let spnego = try Spnego("HTTP@\(hostname)")

      // try to accept the token
      let (username, outputToken) = try spnego.accept(base64Token: inputToken)

      // check if the server has another token to reply to the client
      if let reply = outputToken {
        response.setHeader(.custom(name: "Authorization"), value:"Negotiate \(reply)")
      }//end if

      // check if authenticated successfully.
      if let user = username {

        // USER AUTHENTICATION SUCCESS.
        // *NOTE* on production servers, you may need an ACL list to check even the authenticated user
        // has the permission to this specific resource.
        response.status = .accepted


        // return the protected resource here.
        response.appendBody(string: "<html><title>Hello, world!</title><body>Welcome, \(user)</body></html>\n")


      }else {

        // this may happen by sort of DNS errors, i.e, client is using a nick name or ip other than a FQDN
        // (full qualified domain name) to access the server.
        response.appendBody(string: "<html><H1>GSS REQUEST TO CALL IT ONCE MORE</H1></html>\n")
      }//end if
    }catch (let err) {

      // access denied
      response.appendBody(string: "<html><H1>AUTHENTICATION FAILED: \(err)</H1></html>\n")
    }//end du

    // Ensure that response.completed() is called when your processing is done.
    response.completed()
	}
}

Issues

We are transitioning to using JIRA for all bugs and support related issues, therefore the GitHub issues has been disabled.

If you find a mistake, bug, or any other helpful suggestion you'd like to make on the docs please head over to http:https://jira.perfect.org:8080/servicedesk/customer/portal/1 and raise it.

A comprehensive list of open issues can be found at http:https://jira.perfect.org:8080/projects/ISS/issues

Further Information

For more information on the Perfect project, please visit perfect.org.

About

An example of Perfect Empty Starter Project with SPNEGO feature

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published