Add network criteria in ACLs to specify policy based on network subnet.

pull/349/head
Clement Michaud 2019-03-27 23:09:01 +01:00 committed by Clément Michaud
parent 3c6e2ae448
commit 8a76b5118d
34 changed files with 8579 additions and 173 deletions

View File

@ -35,7 +35,7 @@ Here is the list of the main available features:
* Password reset with identity verification using email.
* Single-factor only authentication method available.
* Access restriction after too many authentication attempts.
* User-defined access control per subdomain and resource.
* Fine-grained access control per subdomain, user, resource and network.
* Support of [basic authentication] for endpoints protected by single factor.
* High-availability using distributed database and KV store.
* Compatible with Kubernetes ingress-nginx controller out of the box.

View File

@ -140,8 +140,15 @@ access_control:
# Rules applied to everyone
- domain: public.example.com
policy: bypass
- domain: secure.example.com
policy: one_factor
# Network based rule, if not provided any network matches.
networks:
- 192.168.1.0/24
- domain: secure.example.com
policy: two_factor
- domain: singlefactor.example.com
policy: one_factor

View File

@ -61,19 +61,8 @@ Here are the versions used for testing in Travis:
### How am I supposed to access the subdomains of example.com?
Well, in order to test Authelia, we will fake your browser that example.com is
served by your machine. To do that, open */etc/hosts* and append the following
lines:
127.0.0.1 home.example.com
127.0.0.1 public.example.com
127.0.0.1 secure.example.com
127.0.0.1 dev.example.com
127.0.0.1 admin.example.com
127.0.0.1 mx1.mail.example.com
127.0.0.1 mx2.mail.example.com
127.0.0.1 singlefactor.example.com
127.0.0.1 login.example.com
Well, in order to test Authelia, Authelia fakes your browser by adding entries
in /etc/hosts when you first source the bootstrap.sh script.
### What should I do if I want to contribute?

View File

@ -0,0 +1,18 @@
version: '2'
services:
kubernetes:
image: nginx:alpine
volumes:
- ./example/compose/nginx/kubernetes/nginx.conf:/etc/nginx/nginx.conf
- ./example/compose/nginx/kubernetes/ssl:/etc/ssl
networks:
authelianet:
aliases:
- public.example.com
- secure.example.com
- login.example.com
- admin.example.com
- dev.example.com
- mail.example.com
# Set the IP to be able to query on port 443
ipv4_address: 192.168.240.100

View File

@ -0,0 +1,30 @@
#
# You can find a documented example of configuration in ./docs/proxies/nginx.md.
#
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 8080 ssl;
resolver 127.0.0.11 ipv6=off;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
location / {
proxy_set_header Host $http_host;
proxy_pass https://192.168.240.1:8080;
}
}
}

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIICATCCAWoCCQCvH2RvyOshNzANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE3MDExNzIzMTc0M1oXDTE4MDExNzIzMTc0M1owRTELMAkG
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzZaE
4XE1QyFNbrHBHRhSA53anAsJ5mBeG7Om6SdQcZAYahlDWEbtdoY4hy0gPNGcITcW
eE+WA+PvNRr7PczKEhneIyUUgV+nrz010fM5JnECPxLTe1oFzl4U8dyYiBpTziNz
hiUfq733PRYjcd9BQtcKcN4LdmQvjUHnnQ73TysCAwEAATANBgkqhkiG9w0BAQsF
AAOBgQAUFICtbuqXgL4HBRAg7yGbwokoH8Ar1QKZGe+F2WTR8vaDLOYUL7VsltLE
EJIGrcfs31nItHOBcLJuflrS8y0CQqes5puRw33LL2usSvO8z2q7JhCx+DSBi6yN
RbhcrGOllIdjsrbmd/zAMBVTUyxSisq3Nmk1cZayDvKg+GSAEA==
-----END CERTIFICATE-----

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQDNloThcTVDIU1uscEdGFIDndqcCwnmYF4bs6bpJ1BxkBhq
GUNYRu12hjiHLSA80ZwhNxZ4T5YD4+81Gvs9zMoSGd4jJRSBX6evPTXR8zkmcQI/
EtN7WgXOXhTx3JiIGlPOI3OGJR+rvfc9FiNx30FC1wpw3gt2ZC+NQeedDvdPKwID
AQABoAAwDQYJKoZIhvcNAQELBQADgYEAmCX60kspIw1Zfb79AQOarFW5Q2K2h5Vx
/cRbDyHlKtbmG77EtICccULyqf76B1gNRw5Zq3lSotSUcLzsWcdesXCFDC7k87Qf
mpQKPj6GdTYJvdWf8aDwt32tAqWuBIRoAbdx5WbFPPWVfDcm7zDJefBrhNUDH0Qd
vcnxjvPMmOM=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDNloThcTVDIU1uscEdGFIDndqcCwnmYF4bs6bpJ1BxkBhqGUNY
Ru12hjiHLSA80ZwhNxZ4T5YD4+81Gvs9zMoSGd4jJRSBX6evPTXR8zkmcQI/EtN7
WgXOXhTx3JiIGlPOI3OGJR+rvfc9FiNx30FC1wpw3gt2ZC+NQeedDvdPKwIDAQAB
AoGBAIwGcfkO30UawJ+daDeF4g5ejI/toM+NYWuiwBNbWJoQl+Bj1o+gt4obvxKq
tKNX7OxelepZ4oZB0CIuf2LHQfU6cVGdu//or7nfS2FLBYStopZyL6KorZbkqsj1
ikQN4GosJQqaYkexnwjItMFaHaRRX6YnIXp42Jl1glitO3+5AkEA+thn/vwFo24I
fC+7ORpmLi+BVAkTuhMm+C6TIV6s64B+A5oQ82OBCYK9YCOWmS6JHHFDrxJla+3M
2U9KXky63wJBANHQCFCirfuT6esSjbqpCeqtmZG5LWHtL12V9DF7yjHPjmHL9uRu
e9W+Uz33IJbqd82gtZ/ARfpYEjD0JEieQTUCQFo872xzDTQ1qSfDo/5u2MNUo5mv
ikEuEp7FYnhmrp4poyt4iRCFgy4Ask+bfdmtO/XXaRnZ7FJfQYoLVB2ITNECQQCN
gOiauZztl4yj5heAVJFDnWF9To61BOp1C7VtyjdL8NfuTUluNrV+KqapnAp2vhue
q0zTOTH47X0XVxFBiLohAkBuQzPey5I3Ui8inE4sDt/fqX8r/GMhBTxIb9KlV/H6
jKZNs/83n5/ohaX36er8svW9PB4pcqENZ+kBpvDtKVwS
-----END RSA PRIVATE KEY-----

View File

@ -6,8 +6,12 @@ services:
- ./example/compose/nginx/portal/nginx.conf:/etc/nginx/nginx.conf
- ./example/compose/nginx/portal/ssl:/etc/ssl
ports:
- "8080:443"
- "8080:8080"
networks:
authelianet:
aliases:
- public.example.com
- secure.example.com
- login.example.com
# Set the IP to be able to query on port 443
ipv4_address: 192.168.240.100

View File

@ -12,7 +12,7 @@ events {
http {
<% if (production) { %>
server {
listen 443 ssl;
listen 8080 ssl;
server_name login.example.com;
resolver 127.0.0.11 ipv6=off;
@ -39,6 +39,10 @@ http {
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
# Needed for network ACLs to work. It appends the IP of the client to the list of IPs
# and allows Authelia to use it to match the network-based ACLs.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_intercept_errors on;
proxy_pass $backend_endpoint;
@ -46,7 +50,7 @@ http {
}
<% } else { %>
server {
listen 443 ssl;
listen 8080 ssl;
server_name login.example.com;
resolver 127.0.0.11 ipv6=off;
@ -59,6 +63,22 @@ http {
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN";
# Serve the backend API for the portal.
location /api {
proxy_set_header Host $http_host;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Needed for network ACLs to work. It appends the IP of the client to the list of IPs
# and allows Authelia to use it to match the network-based ACLs.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_intercept_errors on;
proxy_pass $backend_endpoint;
}
# Serves the portal application.
location / {
# Allow websockets for webpack to auto-reload.
@ -68,23 +88,12 @@ http {
proxy_pass $frontend_endpoint;
}
# Serve the backend API for the portal.
location /api {
proxy_set_header Host $http_host;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_intercept_errors on;
proxy_pass $backend_endpoint;
}
}
<% } %>
# Serves the home page.
server {
listen 443 ssl;
listen 8080 ssl;
server_name home.example.com;
resolver 127.0.0.11 ipv6=off;
@ -104,7 +113,7 @@ http {
# Example configuration of domains protected by Authelia.
server {
listen 443 ssl;
listen 8080 ssl;
server_name public.example.com
admin.example.com
secure.example.com
@ -188,21 +197,9 @@ http {
}
}
# Matches all domains. It redirects to the home page.
server {
listen 443 ssl;
server_name _;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
return 301 https://home.example.com:8080/;
}
# Fake Web Mail used to receive emails sent by Authelia.
server {
listen 443 ssl;
listen 8080 ssl;
server_name mail.example.com;
resolver 127.0.0.11 ipv6=off;
@ -239,5 +236,16 @@ http {
proxy_pass $upstream_endpoint;
}
}
# Matches all domains. It redirects to the home page.
server {
listen 8080 ssl;
server_name _;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
return 301 https://home.example.com:8080/;
}
}

View File

@ -0,0 +1,19 @@
version: '2'
services:
# Simulates client 1.
client-1:
image: sameersbn/squid:3.5.27-1
volumes:
- ./example/compose/squid/squid.conf:/etc/squid/squid.conf
networks:
authelianet:
# Set the IP to be able to query on port 443
ipv4_address: 192.168.240.201
client-2:
image: sameersbn/squid:3.5.27-1
volumes:
- ./example/compose/squid/squid.conf:/etc/squid/squid.conf
networks:
authelianet:
# Set the IP to be able to query on port 443
ipv4_address: 192.168.240.202

File diff suppressed because it is too large Load Diff

8
package-lock.json generated
View File

@ -4032,6 +4032,14 @@
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
"dev": true
},
"ip-range-check": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/ip-range-check/-/ip-range-check-0.0.2.tgz",
"integrity": "sha1-YFyFloeqTxhGORjUYZDYs2maKTw=",
"requires": {
"ipaddr.js": "1.6.0"
}
},
"ipaddr.js": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz",

View File

@ -42,6 +42,7 @@
"express-request-id": "^1.4.0",
"express-session": "^1.14.2",
"helmet": "^3.12.0",
"ip-range-check": "0.0.2",
"ldapjs": "^1.0.2",
"mongodb": "^3.0.5",
"nedb": "^1.8.0",

View File

@ -39,18 +39,22 @@ async function checkHostsFile() {
const actualEntries = fs.readFileSync("/etc/hosts").toString("utf-8")
.split("\n").filter(l => l !== '').map(l => l.split(" ").filter(w => w !== ''));
await checkAndFixEntry(actualEntries, 'login.example.com', '127.0.0.1');
await checkAndFixEntry(actualEntries, 'admin.example.com', '127.0.0.1');
await checkAndFixEntry(actualEntries, 'singlefactor.example.com', '127.0.0.1');
await checkAndFixEntry(actualEntries, 'dev.example.com', '127.0.0.1');
await checkAndFixEntry(actualEntries, 'home.example.com', '127.0.0.1');
await checkAndFixEntry(actualEntries, 'mx1.mail.example.com', '127.0.0.1');
await checkAndFixEntry(actualEntries, 'mx2.mail.example.com', '127.0.0.1');
await checkAndFixEntry(actualEntries, 'public.example.com', '127.0.0.1');
await checkAndFixEntry(actualEntries, 'secure.example.com', '127.0.0.1');
await checkAndFixEntry(actualEntries, 'mail.example.com', '127.0.0.1');
await checkAndFixEntry(actualEntries, 'login.example.com', '192.168.240.100');
await checkAndFixEntry(actualEntries, 'admin.example.com', '192.168.240.100');
await checkAndFixEntry(actualEntries, 'singlefactor.example.com', '192.168.240.100');
await checkAndFixEntry(actualEntries, 'dev.example.com', '192.168.240.100');
await checkAndFixEntry(actualEntries, 'home.example.com', '192.168.240.100');
await checkAndFixEntry(actualEntries, 'mx1.mail.example.com', '192.168.240.100');
await checkAndFixEntry(actualEntries, 'mx2.mail.example.com', '192.168.240.100');
await checkAndFixEntry(actualEntries, 'public.example.com', '192.168.240.100');
await checkAndFixEntry(actualEntries, 'secure.example.com', '192.168.240.100');
await checkAndFixEntry(actualEntries, 'mail.example.com', '192.168.240.100');
await checkAndFixEntry(actualEntries, 'duo.example.com', '192.168.240.100');
// For testing network ACLs.
await checkAndFixEntry(actualEntries, 'proxy-client1.example.com', '192.168.240.201');
await checkAndFixEntry(actualEntries, 'proxy-client2.example.com', '192.168.240.202');
await checkAndFixEntry(actualEntries, 'proxy-client3.example.com', '192.168.240.203');
}
async function checkKubernetesDependencies() {

View File

@ -14,10 +14,10 @@ describe("authorization/Authorizer", function () {
configuration = undefined;
authorizer = new Authorizer(configuration, winston);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1", "group2"]}), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc"}, {user: "user1", groups: ["group1", "group2"]}), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user2", groups: ["group1", "group2"]}), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "admin.example.com", resource: "/"}, {user: "user3", groups: ["group3"]}), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1", "group2"]}, "127.0.0.1"), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc"}, {user: "user1", groups: ["group1", "group2"]}, "127.0.0.1"), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user2", groups: ["group1", "group2"]}, "127.0.0.1"), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "admin.example.com", resource: "/"}, {user: "user3", groups: ["group3"]}, "127.0.0.1"), Level.BYPASS);
});
});
@ -36,7 +36,7 @@ describe("authorization/Authorizer", function () {
});
it("should deny access when no rule is provided", function () {
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
});
it("should control access when multiple domain matcher is provided", function () {
@ -46,10 +46,10 @@ describe("authorization/Authorizer", function () {
subject: "user:user1",
resources: [".*"]
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "mx1.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "mx1.server.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "mx1.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "mx1.server.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
});
it("should allow access to all resources when resources is not provided", function () {
@ -58,10 +58,10 @@ describe("authorization/Authorizer", function () {
policy: "two_factor",
subject: "user:user1"
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "mx1.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "mx1.server.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "mx1.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "mx1.server.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
});
describe("check user rules", function () {
@ -72,9 +72,9 @@ describe("authorization/Authorizer", function () {
resources: [".*"],
subject: "user:user1"
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/another/resource"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "another.home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/another/resource"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "another.home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
});
it("should deny to other users", function () {
@ -84,9 +84,9 @@ describe("authorization/Authorizer", function () {
resources: [".*"],
subject: "user:user1"
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user2", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/another/resource"}, {user: "user2", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "another.home.example.com", resource: "/"}, {user: "user2", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user2", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/another/resource"}, {user: "user2", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "another.home.example.com", resource: "/"}, {user: "user2", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
});
it("should allow user access only to specific resources", function () {
@ -96,16 +96,16 @@ describe("authorization/Authorizer", function () {
resources: ["/private/.*", "^/begin", "/end$"],
subject: "user:user1"
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/class"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/middle/private/class"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/class"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/middle/private/class"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/begin"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/not/begin"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/begin"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/not/begin"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc/end"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc/end/x"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc/end"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc/end/x"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
});
it("should allow access to multiple domains", function () {
@ -125,10 +125,10 @@ describe("authorization/Authorizer", function () {
resources: [".*"],
subject: "user:user1"
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home1.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.ONE_FACTOR);
Assert.equal(authorizer.authorization({domain: "home2.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home3.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home1.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.ONE_FACTOR);
Assert.equal(authorizer.authorization({domain: "home2.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home3.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
});
it("should apply rules in order", function () {
@ -149,9 +149,9 @@ describe("authorization/Authorizer", function () {
subject: "user:user1"
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/poney"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/private/duck"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/private/resource"}, {user: "user1", groups: ["group1"]}), Level.ONE_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/poney"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/private/duck"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/private/resource"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.ONE_FACTOR);
});
});
@ -174,13 +174,13 @@ describe("authorization/Authorizer", function () {
subject: "group:group2"
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"},
{user: "user1", groups: ["group1", "group2", "group3"]}), Level.TWO_FACTOR);
{user: "user1", groups: ["group1", "group2", "group3"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/test"},
{user: "user1", groups: ["group1", "group2", "group3"]}), Level.ONE_FACTOR);
{user: "user1", groups: ["group1", "group2", "group3"]}, "127.0.0.1"), Level.ONE_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"},
{user: "user1", groups: ["group1", "group2", "group3"]}), Level.DENY);
{user: "user1", groups: ["group1", "group2", "group3"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "another.home.example.com", resource: "/"},
{user: "user1", groups: ["group1", "group2", "group3"]}), Level.DENY);
{user: "user1", groups: ["group1", "group2", "group3"]}, "127.0.0.1"), Level.DENY);
});
});
});
@ -197,13 +197,13 @@ describe("authorization/Authorizer", function () {
resources: ["^/private$"]
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"},
{user: "user1", groups: ["group1", "group2", "group3"]}), Level.BYPASS);
{user: "user1", groups: ["group1", "group2", "group3"]}, "127.0.0.1"), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"},
{user: "user1", groups: ["group1", "group2", "group3"]}), Level.DENY);
{user: "user1", groups: ["group1", "group2", "group3"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"},
{user: "user4", groups: ["group5"]}), Level.BYPASS);
{user: "user4", groups: ["group5"]}, "127.0.0.1"), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"},
{user: "user4", groups: ["group5"]}), Level.DENY);
{user: "user4", groups: ["group5"]}, "127.0.0.1"), Level.DENY);
});
});
@ -213,9 +213,9 @@ describe("authorization/Authorizer", function () {
});
it("should allow access to anything when no rule is provided", function () {
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/test"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/test"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.BYPASS);
});
it("should deny access to one resource when defined", function () {
@ -225,9 +225,9 @@ describe("authorization/Authorizer", function () {
resources: ["/test"],
subject: "user:user1"
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/test"}, {user: "user1", groups: ["group1"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/test"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "user1", groups: ["group1"]}, "127.0.0.1"), Level.BYPASS);
});
});
@ -267,32 +267,32 @@ describe("authorization/Authorizer", function () {
subject: "user:harry"
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "admin", groups: ["admins"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "admin", groups: ["admins"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "admin", groups: ["admins"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "admin", groups: ["admins"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "admin", groups: ["admins"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "admin", groups: ["admins"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "admin", groups: ["admins"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "admin", groups: ["admins"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "john", groups: ["dev", "admin-private"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev", "admin-private"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "john", groups: ["dev", "admin-private"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "john", groups: ["dev", "admin-private"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "john", groups: ["dev", "admin-private"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "john", groups: ["dev", "admin-private"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev", "admin-private"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "john", groups: ["dev", "admin-private"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "john", groups: ["dev", "admin-private"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "john", groups: ["dev", "admin-private"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "john", groups: ["dev", "admin-private"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "harry", groups: ["dev"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "harry", groups: ["dev"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "harry", groups: ["dev"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "harry", groups: ["dev"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "harry", groups: ["dev"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "harry", groups: ["dev"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "harry", groups: ["dev"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "harry", groups: ["dev"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "harry", groups: ["dev"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "harry", groups: ["dev"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "harry", groups: ["dev"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "harry", groups: ["dev"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "harry", groups: ["dev"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "harry", groups: ["dev"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "harry", groups: ["dev"]}, "127.0.0.1"), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "harry", groups: ["dev"]}, "127.0.0.1"), Level.TWO_FACTOR);
});
it("should allow when allowed at group level and denied at user level", function () {
@ -308,8 +308,8 @@ describe("authorization/Authorizer", function () {
subject: "group:dev"
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}, "127.0.0.1"), Level.DENY);
});
it("should allow access when allowed at 'any' level and denied at user level", function () {
@ -324,8 +324,8 @@ describe("authorization/Authorizer", function () {
resources: ["^/dev/?.*$"]
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}, "127.0.0.1"), Level.DENY);
});
it("should allow access when allowed at 'any' level and denied at group level", function () {
@ -340,8 +340,8 @@ describe("authorization/Authorizer", function () {
resources: ["^/dev/?.*$"]
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.DENY);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}, "127.0.0.1"), Level.DENY);
});
it("should respect rules precedence", function () {
@ -364,8 +364,36 @@ describe("authorization/Authorizer", function () {
resources: ["^/dev/?.*$"]
}];
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}, "127.0.0.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}, "127.0.0.1"), Level.TWO_FACTOR);
});
});
describe("check network rules", function () {
beforeEach(function () {
configuration.rules = [{
domain: "home.example.com",
policy: "one_factor",
subject: "user:john",
networks: ["192.168.0.0/24", "10.0.0.0/8"]
},
{
domain: "home.example.com",
policy: "two_factor",
subject: "user:john",
},
{
domain: "public.example.com",
policy: "bypass",
networks: ["10.0.0.0/8"]
}];
});
it("should respect network ranges", function() {
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}, "192.168.4.1"), Level.TWO_FACTOR);
Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}, "192.168.0.5"), Level.ONE_FACTOR);
Assert.equal(authorizer.authorization({domain: "public.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}, "10.1.3.0"), Level.BYPASS);
Assert.equal(authorizer.authorization({domain: "public.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}, "11.1.3.0"), Level.DENY);
});
});
});

View File

@ -6,6 +6,7 @@ import { MultipleDomainMatcher } from "./MultipleDomainMatcher";
import { Level } from "./Level";
import { Object } from "./Object";
import { Subject } from "./Subject";
const IpRangeCheck = require("ip-range-check");
function MatchDomain(actualDomain: string) {
return function (rule: ACLRule): boolean {
@ -44,6 +45,16 @@ function MatchSubject(subject: Subject) {
};
}
function MatchNetworks(ip: string) {
return (rule: ACLRule): boolean => {
if (!rule.networks) return true; // all networks match
return rule.networks
.map(net => IpRangeCheck(ip, net) as boolean)
.reduce((acc, v) => acc || v, false);
};
}
export class Authorizer implements IAuthorizer {
private readonly configuration: ACLConfiguration;
@ -51,13 +62,14 @@ export class Authorizer implements IAuthorizer {
this.configuration = configuration;
}
private getMatchingRules(object: Object, subject: Subject): ACLRule[] {
private getMatchingRules(object: Object, subject: Subject, ip: string): ACLRule[] {
const rules = this.configuration.rules;
if (!rules) return [];
return rules
.filter(MatchDomain(object.domain))
.filter(MatchResource(object.resource))
.filter(MatchSubject(subject));
.filter(MatchSubject(subject))
.filter(MatchNetworks(ip));
}
private ruleToLevel(policy: ACLPolicy): Level {
@ -71,10 +83,10 @@ export class Authorizer implements IAuthorizer {
return Level.DENY;
}
authorization(object: Object, subject: Subject): Level {
authorization(object: Object, subject: Subject, ip: string): Level {
if (!this.configuration) return Level.BYPASS;
const rules = this.getMatchingRules(object, subject);
const rules = this.getMatchingRules(object, subject, ip);
return (rules.length > 0)
? this.ruleToLevel(rules[0].policy) // extract the policy of the first matching rule

View File

@ -11,7 +11,7 @@ export default class AuthorizerStub implements IAuthorizer {
this.authorizationMock = Sinon.stub();
}
authorization(object: Object, subject: Subject): Level {
return this.authorizationMock(object, subject);
authorization(object: Object, subject: Subject, ip: string): Level {
return this.authorizationMock(object, subject, ip);
}
}

View File

@ -3,5 +3,5 @@ import { Subject } from "./Subject";
import { Object } from "./Object";
export interface IAuthorizer {
authorization(object: Object, subject: Subject): Level;
authorization(object: Object, subject: Subject, ip: string): Level;
}

View File

@ -1,11 +1,14 @@
export type ACLPolicy = "deny" | "bypass" | "one_factor" | "two_factor";
export type ACLNetwork = string[];
export type ACLRule = {
domain: string;
resources?: string[];
subject?: string;
policy: ACLPolicy;
networks?: ACLNetwork;
};
export interface ACLConfiguration {

View File

@ -83,7 +83,7 @@ export default function (vars: ServerVariables) {
groups: authSession.groups
};
const authorizationLevel = vars.authorizer.authorization(resObject, subject);
const authorizationLevel = vars.authorizer.authorization(resObject, subject, req.ip);
if (authorizationLevel <= AuthorizationLevel.ONE_FACTOR) {
if (IsRedirectionSafe(vars, new URLParse(targetUrl))) {
res.json({redirect: targetUrl});

View File

@ -11,21 +11,21 @@ describe('routes/verify/CheckAuthorizations', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.BYPASS);
CheckAuthorizations(authorizer, "public.example.com", "/index.html", undefined,
undefined, Level.NOT_AUTHENTICATED);
undefined, "127.0.0.1", Level.NOT_AUTHENTICATED);
});
it('should allow an authenticated user (1FA)', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.BYPASS);
CheckAuthorizations(authorizer, "public.example.com", "/index.html", "john",
["group1", "group2"], Level.ONE_FACTOR);
["group1", "group2"], "127.0.0.1", Level.ONE_FACTOR);
});
it('should allow an authenticated user (2FA)', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.BYPASS);
CheckAuthorizations(authorizer, "public.example.com", "/index.html", "john",
["group1", "group2"], Level.TWO_FACTOR);
["group1", "group2"], "127.0.0.1", Level.TWO_FACTOR);
});
});
@ -34,21 +34,21 @@ describe('routes/verify/CheckAuthorizations', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.ONE_FACTOR);
Assert.throws(() => { CheckAuthorizations(authorizer, "public.example.com", "/index.html", undefined,
undefined, Level.NOT_AUTHENTICATED) }, NotAuthenticatedError);
undefined, "127.0.0.1", Level.NOT_AUTHENTICATED) }, NotAuthenticatedError);
});
it('should allow an authenticated user (1FA)', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.ONE_FACTOR);
CheckAuthorizations(authorizer, "public.example.com", "/index.html", "john",
["group1", "group2"], Level.ONE_FACTOR);
["group1", "group2"], "127.0.0.1", Level.ONE_FACTOR);
});
it('should allow an authenticated user (2FA)', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.ONE_FACTOR);
CheckAuthorizations(authorizer, "public.example.com", "/index.html", "john",
["group1", "group2"], Level.TWO_FACTOR);
["group1", "group2"], "127.0.0.1", Level.TWO_FACTOR);
});
});
@ -57,21 +57,21 @@ describe('routes/verify/CheckAuthorizations', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
Assert.throws(() => CheckAuthorizations(authorizer, "public.example.com", "/index.html", undefined,
undefined, Level.NOT_AUTHENTICATED), NotAuthenticatedError);
undefined, "127.0.0.1", Level.NOT_AUTHENTICATED), NotAuthenticatedError);
});
it('should not allow an authenticated user (1FA)', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
Assert.throws(() => CheckAuthorizations(authorizer, "public.example.com", "/index.html", "john",
["group1", "group2"], Level.ONE_FACTOR), NotAuthenticatedError);
["group1", "group2"], "127.0.0.1", Level.ONE_FACTOR), NotAuthenticatedError);
});
it('should allow an authenticated user (2FA)', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
CheckAuthorizations(authorizer, "public.example.com", "/index.html", "john",
["group1", "group2"], Level.TWO_FACTOR);
["group1", "group2"], "127.0.0.1", Level.TWO_FACTOR);
});
});
@ -80,21 +80,21 @@ describe('routes/verify/CheckAuthorizations', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.DENY);
Assert.throws(() => CheckAuthorizations(authorizer, "public.example.com", "/index.html", undefined,
undefined, Level.NOT_AUTHENTICATED), NotAuthenticatedError);
undefined, "127.0.0.1", Level.NOT_AUTHENTICATED), NotAuthenticatedError);
});
it('should not allow an authenticated user (1FA)', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.DENY);
Assert.throws(() => CheckAuthorizations(authorizer, "public.example.com", "/index.html", "john",
["group1", "group2"], Level.ONE_FACTOR), NotAuthorizedError);
["group1", "group2"], "127.0.0.1", Level.ONE_FACTOR), NotAuthorizedError);
});
it('should not allow an authenticated user (2FA)', function() {
const authorizer = new AuthorizerStub();
authorizer.authorizationMock.returns(AuthorizationLevel.DENY);
Assert.throws(() => CheckAuthorizations(authorizer, "public.example.com", "/index.html", "john",
["group1", "group2"], Level.TWO_FACTOR), NotAuthorizedError);
["group1", "group2"], "127.0.0.1", Level.TWO_FACTOR), NotAuthorizedError);
});
});
});

View File

@ -25,11 +25,11 @@ function isAuthorized(
export default function (
authorizer: IAuthorizer,
domain: string, resource: string,
user: string, groups: string[],
user: string, groups: string[], ip: string,
authenticationLevel: AuthenticationLevel): AuthorizationLevel {
const authorizationLevel = authorizer
.authorization({domain, resource}, {user, groups});
.authorization({domain, resource}, {user, groups}, ip);
if (authorizationLevel == AuthorizationLevel.BYPASS) {
return authorizationLevel;

View File

@ -41,7 +41,7 @@ export default async function(req: Express.Request, res: Express.Response,
const uri = GetHeader(req, HEADER_X_ORIGINAL_URL);
const urlDecomposition = URLDecomposer.fromUrl(uri);
const authorizationLevel = CheckAuthorizations(vars.authorizer, urlDecomposition.domain, urlDecomposition.path,
username, groupsAndEmails.groups, Level.ONE_FACTOR);
username, groupsAndEmails.groups, req.ip, Level.ONE_FACTOR);
if (authorizationLevel > AuthorizationLevel.BYPASS) {
setUserAndGroupsHeaders(res, username, groupsAndEmails.groups);

View File

@ -31,9 +31,9 @@ export default async function (req: Express.Request, res: Express.Response,
const username = authSession.userid;
const groups = authSession.groups;
vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", d.domain,
d.path, (username) ? username : "unknown", (groups instanceof Array && groups.length > 0) ? groups.join(",") : "unknown");
const authorizationLevel = CheckAuthorizations(vars.authorizer, d.domain, d.path, username, groups,
vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s, ip=%s", d.domain,
d.path, (username) ? username : "unknown", (groups instanceof Array && groups.length > 0) ? groups.join(",") : "unknown", req.ip);
const authorizationLevel = CheckAuthorizations(vars.authorizer, d.domain, d.path, username, groups, req.ip,
authSession.authentication_level);
if (authorizationLevel > AuthorizationLevel.BYPASS) {

View File

@ -1,19 +1,23 @@
require("chromedriver");
import chrome from 'selenium-webdriver/chrome';
import SeleniumWebdriver, { WebDriver } from "selenium-webdriver";
import SeleniumWebdriver, { WebDriver, ProxyConfig } from "selenium-webdriver";
export async function StartDriver() {
export async function StartDriver(proxy?: ProxyConfig) {
let options = new chrome.Options();
if (process.env['HEADLESS'] == 'y') {
options = options.headless();
}
const driver = new SeleniumWebdriver.Builder()
.forBrowser("chrome")
.setChromeOptions(options)
.build();
return driver;
let driverBuilder = new SeleniumWebdriver.Builder()
.forBrowser("chrome");
if (proxy) {
options = options.addArguments(`--proxy-server=${proxy.httpProxy}`)
}
driverBuilder = driverBuilder.setChromeOptions(options);
return await driverBuilder.build();
}
export async function StopDriver(driver: WebDriver) {

View File

@ -4,7 +4,6 @@ import VerifyIsOneTimePasswordView from "../../../helpers/assertions/VerifyIsOne
import ClickOnLink from "../../../helpers/ClickOnLink";
import VerifyIsUseAnotherMethodView from "../../../helpers/assertions/VerifyIsUseAnotherMethodView";
import ClickOnButton from "../../../helpers/behaviors/ClickOnButton";
import VerifyIsSecurityKeyView from "../../../helpers/assertions/VerifyIsSecurityKeyView";
import VerifyIsSecondFactorStage from "../../../helpers/assertions/VerifyIsSecondFactorStage";
import VerifyIsDuoPushNotificationView from "../../../helpers/assertions/VerifyIsDuoPushNotificationView";

View File

@ -6,6 +6,7 @@ import { spawn, execSync, ChildProcess } from 'child_process';
import treeKill = require('tree-kill');
import Redis, { RedisClient } from 'redis';
import sleep from '../../helpers/utils/sleep';
import DockerEnvironment from '../../helpers/context/DockerEnvironment';
let portFowardingProcess: ChildProcess;
@ -78,12 +79,20 @@ async function redisReady(kubernetes: Kubernetes): Promise<void> {
function startAutheliaPortForwarding(kubernetes: Kubernetes) {
// Serve applications on port 8080
portFowardingProcess = spawn('kubectl',
['port-forward', '-n', 'authelia', 'service/nginx-ingress-controller-service', '8080:443'], {
portFowardingProcess = spawn('kubectl port-forward --address 0.0.0.0 -n authelia service/nginx-ingress-controller-service 8080:443', {
shell: true,
env: {KUBECONFIG: kubernetes.kubeConfig, ...process.env}
});
} as any);
portFowardingProcess.stdout.pipe(process.stdout);
portFowardingProcess.stderr.pipe(process.stderr);
}
const dockerEnv = new DockerEnvironment([
'docker-compose.yml',
'example/compose/nginx/kubernetes/docker-compose.yml',
]);
async function setup() {
let kubernetes: Kubernetes;
if (!process.env['KUBECONFIG']) {
@ -108,6 +117,8 @@ async function setup() {
});
await servicesReady(kubernetes);
await dockerEnv.start();
startAutheliaPortForwarding(kubernetes);
}
@ -119,8 +130,10 @@ async function teardown() {
await sleep(1000);
}
// if (process.env['KUBECONFIG']) return;
// await KubernetesManager.delete();
await dockerEnv.stop();
if (process.env['KUBECONFIG']) return;
await KubernetesManager.delete();
}
const setup_timeout = 300000;

View File

@ -0,0 +1,13 @@
# Basic suite
This suite has been created to test Authelia with basic feature in a non highly-available setup.
Authelia basically use an in-memory cache to store user sessions and persist data on disk instead
of using a remote database. Also, the user accounts are stored in file-based database.
## Components
Authelia, nginx, fake webmail for registering devices.
## Tests
Broad range of tests.

View File

@ -0,0 +1,64 @@
###############################################################
# Authelia minimal configuration #
###############################################################
port: 9091
logs_level: debug
authentication_backend:
file:
path: ./test/suites/basic/users_database.test.yml
session:
secret: unsecure_session_secret
domain: example.com
expiration: 3600000 # 1 hour
inactivity: 300000 # 5 minutes
# Configuration of the storage backend used to store data and secrets. i.e. totp data
storage:
local:
path: /tmp/authelia/db
# Access Control
#
# Access control is a set of rules you can use to restrict user access to certain
# resources.
access_control:
default_policy: deny
rules:
- domain: secure.example.com
policy: one_factor
networks:
- 192.168.240.201/32
- domain: secure.example.com
policy: bypass
networks:
- 192.168.240.202/32
- 192.168.240.203/32
- domain: secure.example.com
policy: two_factor
# Configuration of the authentication regulation mechanism.
regulation:
# Set it to 0 to disable max_retries.
max_retries: 3
# The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window.
find_time: 300
# The length of time before a banned user can login again.
ban_time: 900
notifier:
# Use a SMTP server for sending notifications
smtp:
username: test
password: password
secure: false
host: 127.0.0.1
port: 1025
sender: admin@example.com

View File

@ -0,0 +1,37 @@
import fs from 'fs';
import { exec } from "../../helpers/utils/exec";
import AutheliaServer from "../../helpers/context/AutheliaServer";
import DockerEnvironment from "../../helpers/context/DockerEnvironment";
const autheliaServer = new AutheliaServer(__dirname + '/config.yml', [__dirname + '/users_database.yml']);
const dockerEnv = new DockerEnvironment([
'docker-compose.yml',
'example/compose/nginx/backend/docker-compose.yml',
'example/compose/nginx/portal/docker-compose.yml',
'example/compose/squid/docker-compose.yml',
'example/compose/smtp/docker-compose.yml',
])
async function setup() {
await exec(`cp ${__dirname}/users_database.yml ${__dirname}/users_database.test.yml`);
await exec('mkdir -p /tmp/authelia/db');
await exec('./example/compose/nginx/portal/render.js ' + (fs.existsSync('.suite') ? '': '--production'));
await dockerEnv.start();
await autheliaServer.start();
}
async function teardown() {
await autheliaServer.stop();
await dockerEnv.stop();
await exec('rm -rf /tmp/authelia/db');
}
const setup_timeout = 30000;
const teardown_timeout = 30000;
export {
setup,
setup_timeout,
teardown,
teardown_timeout
};

View File

@ -0,0 +1,77 @@
import { StartDriver, StopDriver } from "../../../helpers/context/WithDriver";
import LoginAndRegisterTotp from "../../../helpers/LoginAndRegisterTotp";
import FillLoginPageAndClick from "../../../helpers/FillLoginPageAndClick";
import ValidateTotp from "../../../helpers/ValidateTotp";
import VerifySecretObserved from "../../../helpers/assertions/VerifySecretObserved";
import VisitPageAndWaitUrlIs from "../../../helpers/behaviors/VisitPageAndWaitUrlIs";
import VerifyUrlIs from "../../../helpers/assertions/VerifyUrlIs";
import VisitPage from "../../../helpers/VisitPage";
async function createClient(id: number) {
return await StartDriver({
proxyType: "manual",
httpProxy: `http://proxy-client${id}.example.com:3128`
});
}
export default function() {
before(async function() {
const driver = await StartDriver();
this.secret = await LoginAndRegisterTotp(driver, "john", "password", true);
if (!this.secret) throw new Error('No secret!');
await StopDriver(driver);
});
describe("Standard client (from public network)", function() {
before(async function() {
this.driver = await StartDriver();
});
after(async function() {
await StopDriver(this.driver);
});
it("should require two factor", async function() {
await VisitPage(this.driver, "https://secure.example.com:8080/secret.html");
await VerifyUrlIs(this.driver, "https://login.example.com:8080/#/?rd=https://secure.example.com:8080/secret.html");
await FillLoginPageAndClick(this.driver, 'john', 'password');
await ValidateTotp(this.driver, this.secret);
await VerifyUrlIs(this.driver, "https://secure.example.com:8080/secret.html");
await VerifySecretObserved(this.driver);
});
})
describe("Client 1 (from network 192.168.240.201/32)", function() {
before(async function() {
this.client1 = await createClient(1);
});
after(async function() {
await StopDriver(this.client1);
});
it("should require one factor", async function() {
await VisitPage(this.client1, "https://secure.example.com:8080/secret.html");
await VerifyUrlIs(this.client1, "https://login.example.com:8080/#/?rd=https://secure.example.com:8080/secret.html");
await FillLoginPageAndClick(this.client1, 'john', 'password');
await VerifyUrlIs(this.client1, "https://secure.example.com:8080/secret.html");
await VerifySecretObserved(this.client1);
});
});
describe("Client 2 (from network 192.168.240.202/32)", function() {
before(async function() {
this.client2 = await createClient(2);
});
after(async function() {
await StopDriver(this.client2);
});
it("should bypass", async function() {
await VisitPageAndWaitUrlIs(this.client2, "https://secure.example.com:8080/secret.html");
await VerifyUrlIs(this.client2, "https://secure.example.com:8080/secret.html");
await VerifySecretObserved(this.client2);
});
});
}

View File

@ -0,0 +1,13 @@
import AutheliaSuite from "../../helpers/context/AutheliaSuite";
import { exec } from '../../helpers/utils/exec';
import NetworkACLs from "./scenarii/NetworkACLs";
AutheliaSuite(__dirname, function() {
this.timeout(10000);
beforeEach(async function() {
await exec(`cp ${__dirname}/users_database.yml ${__dirname}/users_database.test.yml`);
});
describe("Network ACLs", NetworkACLs);
});

View File

@ -0,0 +1,29 @@
###############################################################
# Users Database #
###############################################################
# This file can be used if you do not have an LDAP set up.
# List of users
users:
john:
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: john.doe@authelia.com
groups:
- admins
- dev
harry:
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: harry.potter@authelia.com
groups: []
bob:
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: bob.dylan@authelia.com
groups:
- dev
james:
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: james.dean@authelia.com