Skip to content

Commit

Permalink
Merge pull request #1 from jaredhendrickson13/development
Browse files Browse the repository at this point in the history
v1.0.0
  • Loading branch information
jaredhendrickson13 authored Feb 20, 2021
2 parents d3b7184 + 4110560 commit 029fec3
Show file tree
Hide file tree
Showing 81 changed files with 27,215 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.idea/
*.DS_Store
92 changes: 90 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,90 @@
# pfsense-saml2-auth
A SAML2 authentication extension for the pfSense UI
# pfSense SAML2 Authentication
`pfsense-saml2-auth` is a packaged SAML2 authentication extension for the pfSense webConfigurator. Currently, pfSense
only supports local, LDAP and RADIUS authentication and does not support any native multi-factor authentication (MFA).
At this time, there is unfortunately no roadmap for native SAML2 authentication or native MFA options on pfSense. With
[pfSense Plus](https://www.netgate.com/blog/pfsense-plus-21-02-release-and-pfsense-ce-2-5-0-release-now-available.html)
being officially announced, it is unlikely we will see enterprise level features like SAML2 implemented in pfSense CE
anytime soon. This can create major headaches when dealing with security compliance standards such as PCI DSS that may
require MFA on firewall admin logins. `pfsense-saml2-auth` helps alleviate this problem by allowing you to integrate
single sign-on (SSO) with an identity provider such as Okta or OneLogin. In doing so, you will be able use the identity
provider's built-in MFA for pfSense logins and greatly simplify user onboarding.<br><br>

![sso_login_example_img](docs/img/sso_login.png)
<sub>The 'Login with SSO' option will only appear on the login screen after the package is installed and configured. SAML2
must be enabled in System > SAML2 for this option to appear.</sub><br>


## Key Features
- Easily integrates SSO logins for pfSense without losing any existing authentication functionality.
- Automatically maps groups returned within the SAML2 assertion to groups within pfSense to inherit existing privileges.
No need to create locate users before authenticating.
- Retains pfSense's built-in authentication and change logs.
- Adds the System > SAML2 settings page within the webConfigurator to make setup a breeze.

## Installation
To install, simply run the following command from the pfSense command line:<br>
`pkg add https://github.com/jaredhendrickson13/pfsense-saml2-auth/releases/latest/download/pfSense-2.5-pkg-saml2-auth.txz`

To uninstall:<br>
`pkg delete pfSense-pkg-saml2-auth`

_Note: when pfSense updates this package will be uninstalled. After updating pfSense, the package will need to be
reinstalled to match the updated version_

## Supported Versions
Currently, the package fully supports the following pfSense versions including patched versions of the same release:
- pfSense 2.5.0-RELEASE
- pfSense 2.4.5-RELEASE
- pfSense 2.4.4-RELEASE

Any version not listed is technically unsupported, but may still function. Proceed with caution.

## Setup
After installation, navigate to System > SAML2 to configure SAML authentication. You will need to obtain a few
items from your IdP to add on this page and you will also need to provide a few items to your IdP from this page.
<br>

![sso_settings_example_img](docs/img/sso_settings.png)

_Note: users must hold the `page-all` and/or `page-system-saml2-auth` privilege to access the System > SAML2 page._

### Privilege Mapping
There are two ways to map pfSense privileges to SAML2 users. Choose the method that bests suits your identity provider's
capabilities and your specific needs:

1) Create pfSense groups to match those that exist within your identity provider. For example,
if you have a group within your identity provider named `Network Admins` that you would like to grant pfSense access to,
you would need to create a group within pfSense named `Network Admins` exactly as it appears in your IdP. Ensure this
group's `Scope` value is set to `Remote` within pfSense. Then assign the desired pfSense privileges to the group. Please
note you must configure your IdP to return a group attribute within the SAML assertion that contains a list of groups
the authenticating user belongs to. You can specify the name of the group mapping attribute in System > SAML2 > Identity
Provider Groups Attribute. If your IdP does not return group attributes in the SAML assertion, this method cannot be
used.
![sso_group_mapping_example_img](docs/img/sso_group_mapping.png)<br><br>

2) Create a local user that matches the authenticating user's username as it appears in your
IdP. You may use a random password for this user to prevent local authentication if needed. After the local user is
created, assign any permissions you would like the user to obtain upon login. Once the user has been created and any
privileges have been assigned, the user will automatically inherit the assigned privileges upon SAML2 logins. Note,
pfSense does not allow emails as local usernames. In the case that your IdP uses email addresses as usernames by
default, you may check the checkbox at System > SAML2 > Filter Email Usernames to only use the username before the @
symbol.
![sso_user_mapping_example_img](docs/img/sso_user_mapping.png)<br><br>

## Limitations
- This package is only intended to add SAML2 authentication to the webConfigurator. SAML2 authentication is not made
available for other pfSense services such as SSH, captive portal, OpenVPN, etc.

## Disclaimers
- This project is in no way affiliated with the pfSense project or it's parent organization Netgate. Any use of the
pfSense name is intended to relate the project to it's developed platform and in no way capitalizes on the
pfSense trademark. By using this software, you acknowledge that no entity can provide support or guarantee
functionality.
- This project was written and tested using Okta as the IdP. While it should support any IdP that supports SAML2
applications, it cannot be guaranteed to work with your specific IdP.
- While extra precautions are taken to keep this package secure, you should always test thoroughly before implementing
in a production environment. Use this software is at your own risk!
- This package was designed to be a simple, secure, and stable SAML2 implementation for pfSense. Because of this,
additional features are unlikely to be added unless there are changes to the SAML standard itself. The package will
continue to receive updates to address security issues and general bugs, as well as changes to accommodate future
versions of pfSense.
Binary file added docs/img/sso_group_mapping.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/sso_login.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/sso_settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/sso_user_mapping.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions pfSense-pkg-saml2-auth/files/etc/inc/priv/saml2_auth.priv.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
// Copyright 2021 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

global $priv_list;

$priv_list['page-system-saml2-auth'] = array();
$priv_list['page-system-saml2-auth']['name'] = "WebCfg - System: SAML2 authentication package";
$priv_list['page-system-saml2-auth']['descr'] = "Allow access to SAML2 authentication package UI";
$priv_list['page-system-saml2-auth']['match'] = array();
$priv_list['page-system-saml2-auth']['match'][] = "saml2_auth*";
163 changes: 163 additions & 0 deletions pfSense-pkg-saml2-auth/files/etc/inc/saml2_auth/SAML2Auth.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php
// Copyright 2021 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("saml2_auth/lib/php-saml-3.5.1/_toolkit_loader.php");
require_once("config.inc");

class SAML2Auth {
public $auth;
private $config;

# Constructs the saml2_auth object by loading SAML2 settings and creating the OneLogin_Saml2_Auth object
public function __construct() {
session_start();
global $config;
$this->config = $config;

# Try to start SAML2 authentication, handle errors accordingly
try {
$this->auth = new OneLogin\Saml2\Auth($this::get_saml_settings());
} catch (OneLogin\Saml2\Error $error) {
echo $error->getMessage().PHP_EOL;
exit();
}
}

# Initiates the SSO login. Requires a URL to redirect to
public function sso($redirect) {
# Mark the SAML2 login as in process and start login
$_SESSION["saml2_started"] = true;
$this->auth->login($redirect);
}

public function acs() {
# Check the state of SAML2 authentication. Only proceeds if in expected state.
$this->__check_saml2_state();

# Remove the saml2_started handler and process our request response
unset($_SESSION['saml2_started']);
$this->auth->processResponse($_SESSION['AuthNRequestID']);

# If the sign on attempt is valid, map attributes to our session array.
if ($this->auth->isAuthenticated()) {
# Set session data
$_SESSION["saml2_auth"] = true;
$_SESSION['saml2_user_data'] = $this->auth->getAttributes();
$_SESSION['saml2_name_id'] = $this->auth->getNameId();
unset($_SESSION['AuthNRequestID']);

# Support RelayState settings
if (isset($_POST['RelayState']) && OneLogin\Saml2\Utils::getSelfURL() != $_POST['RelayState']) {
$this->auth->redirectTo($_POST['RelayState']);
}
}

# Handle SAML errors
$this->get_saml2_errors();
}

public function metadata() {
try {
# Validate the SP metadata and print the XML metadata if valid
$settings = new OneLogin\Saml2\Settings($this->get_saml_settings(), true);
$metadata = $settings->getSPMetadata();
$errors = $settings->validateMetadata($metadata);
if (empty($errors)) {
header('Content-Type: text/xml');
echo $metadata;
} else {
throw new OneLogin\Saml2\Error (
'Invalid SP metadata: '.implode(', ', $errors),
OneLogin\Saml2\Error::METADATA_SP_INVALID
);
}
} catch (Exception $e) {
echo $e->getMessage();
}
}

private function __check_saml2_state() {
# Redirect to home page if user is already logged in
if ($_SESSION['Logged_In']) {
header("Location: /saml2_auth/sso/redirect/");
exit();
}
# Redirect to SSO login if the login process has not been started
elseif (!$_SESSION['saml2_started']) {
header("Location: /saml2_auth/sso/");
exit();
}
}

public function get_saml2_errors() {
# Print SAML errors if they exist
if (!empty($this->auth->getErrors())) {
session_destroy();
echo '<pre>',implode(', ', $this->auth->getErrors()),'</pre>';
if ($this->auth->getSettings()->isDebugActive()) {
echo '<pre>'.$this->auth->getLastErrorReason().'</pre>';
exit();
}
}
}

public static function get_package_config() {
global $config;
# Only proceed if there are installed packages. Prevents PHP warnings.
if (is_array($config["installedpackages"]["package"])) {
# Loop through each installed package until we find the saml2-auth package
foreach ($config["installedpackages"]["package"] as $id=>$pkg) {
if ($pkg["internal_name"] === "saml2-auth") {
return [$id, $pkg["conf"]];
}
}
}
}

public static function get_saml_settings() {
# Local variables
$pkg_conf = SAML2Auth::get_package_config()[1];
$php_saml_config = array (
'debug' => boolval($pkg_conf["debug_mode"]),
'sp' => array (
'entityId' => $pkg_conf["sp_base_url"].'/saml2_auth/sso/metadata/',
'assertionConsumerService' => array (
'url' => $pkg_conf["sp_base_url"].'/saml2_auth/sso/acs/',
),
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
),
'idp' => array (
'entityId' => $pkg_conf["idp_entity_id"],
'singleSignOnService' => array (
'url' => $pkg_conf["idp_sign_on_url"],
),
'x509cert' => base64_decode($pkg_conf["idp_x509_cert"])
),
);

# When custom parameters are configured, update the config to include them
if (!empty($pkg_conf["custom_conf"])) {
# Decode the custom configuration values. This should be a Base64 encoded JSON string
$custom_conf = json_decode(base64_decode($pkg_conf["custom_conf"]), true);
# Only merge custom configuration in if it decodes to an array
if (is_array($custom_conf)) {
$php_saml_config = array_merge_recursive($php_saml_config, $custom_conf);
}
}
return $php_saml_config;
}


}
Loading

0 comments on commit 029fec3

Please sign in to comment.