Compare commits
20 Commits
main
...
r-0b41ca6e
Author | SHA1 | Date |
---|---|---|
|
e357e6f702 | |
|
3ed6824f75 | |
|
cad768c774 | |
|
98b93e1d13 | |
|
906367dd8f | |
|
3cc17fbf3e | |
|
125422c9e4 | |
|
92624216ac | |
|
7d3d41f5a7 | |
|
3e431ac0a4 | |
|
f77baed26b | |
|
db66835959 | |
|
9cd6875813 | |
|
d3a64c038c | |
|
97f15299bc | |
|
62ef974a8c | |
|
25df33c3d5 | |
|
3cc57959b7 | |
|
166270dc12 | |
|
28fbedcc5a |
|
@ -0,0 +1,61 @@
|
|||
apiVersion: "operator.nevis-security.ch/v1"
|
||||
kind: "NevisComponent"
|
||||
metadata:
|
||||
name: "nai"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
||||
labels:
|
||||
deploymentTarget: "nai"
|
||||
annotations:
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "6ec6739e824c8e56d9633622"
|
||||
spec:
|
||||
type: "NevisAuth"
|
||||
replicas: 1
|
||||
version: "8.2405.2"
|
||||
gitInitVersion: "1.3.0"
|
||||
runAsNonRoot: true
|
||||
ports:
|
||||
management: 9000
|
||||
soap: 8991
|
||||
resources:
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "2000Mi"
|
||||
requests:
|
||||
cpu: "20m"
|
||||
memory: "1000Mi"
|
||||
livenessProbe:
|
||||
soap:
|
||||
tcpSocket: true
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 4
|
||||
readinessProbe:
|
||||
management:
|
||||
httpGet:
|
||||
path: "/nevisauth/liveness"
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 6
|
||||
startupProbe:
|
||||
management:
|
||||
httpGet:
|
||||
path: "/nevisauth/liveness"
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 6
|
||||
failureThreshold: 50
|
||||
podDisruptionBudget:
|
||||
maxUnavailable: "50%"
|
||||
git:
|
||||
tag: "r-0b41ca6ec8b7dd7ee68a9f8207d07d9aa40564eb"
|
||||
dir: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT/DEFAULT-ADN-POST-IAM-TKNXCHNG-INV/nai"
|
||||
credentials: "git-credentials"
|
||||
keystores:
|
||||
- "nai-default-identity"
|
||||
- "nai-sh4r3d-default-default-signer"
|
||||
truststores:
|
||||
- "nai-default-tls-client-trust"
|
||||
- "nai-default-default-signer-trust"
|
||||
- "nai-default-tls-trust"
|
||||
podSecurity:
|
||||
policy: "baseline"
|
||||
automountServiceAccountToken: false
|
||||
timeZone: "Europe/Zurich"
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: "operator.nevis-security.ch/v1"
|
||||
kind: "NevisTrustStore"
|
||||
metadata:
|
||||
name: "nai-default-default-signer-trust"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
||||
labels:
|
||||
deploymentTarget: "nai"
|
||||
annotations:
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "6ec6739e824c8e56d9633622"
|
||||
spec:
|
||||
keystores:
|
||||
- name: "nai-sh4r3d-default-default-signer"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: "operator.nevis-security.ch/v1"
|
||||
kind: "NevisKeyStore"
|
||||
metadata:
|
||||
name: "nai-default-identity"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
||||
labels:
|
||||
deploymentTarget: "nai"
|
||||
annotations:
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "6ec6739e824c8e56d9633622"
|
||||
spec:
|
||||
cn: "nai"
|
||||
usage: "<reserved for future use>"
|
||||
san:
|
||||
dns:
|
||||
- "nai"
|
||||
- "nai.adn-postit-tknxchng-01-dev"
|
||||
email: []
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: "operator.nevis-security.ch/v1"
|
||||
kind: "NevisTrustStore"
|
||||
metadata:
|
||||
name: "nai-default-tls-client-trust"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
||||
labels:
|
||||
deploymentTarget: "nai"
|
||||
annotations:
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "6ec6739e824c8e56d9633622"
|
||||
spec:
|
||||
keystores:
|
||||
- name: "npi-cossa-realm-identity"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: "operator.nevis-security.ch/v1"
|
||||
kind: "NevisTrustStore"
|
||||
metadata:
|
||||
name: "nai-default-tls-trust"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
||||
labels:
|
||||
deploymentTarget: "nai"
|
||||
annotations:
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "6ec6739e824c8e56d9633622"
|
||||
spec:
|
||||
keystores: []
|
|
@ -0,0 +1,16 @@
|
|||
apiVersion: "operator.nevis-security.ch/v1"
|
||||
kind: "NevisKeyStore"
|
||||
metadata:
|
||||
name: "nai-sh4r3d-default-default-signer"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
||||
labels:
|
||||
deploymentTarget: "nai"
|
||||
annotations:
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "6ec6739e824c8e56d9633622"
|
||||
spec:
|
||||
cn: "signer"
|
||||
usage: "signer"
|
||||
san:
|
||||
dns: []
|
||||
email: []
|
|
@ -0,0 +1,18 @@
|
|||
schemaVersion: 1.0
|
||||
instance:
|
||||
type: "nevisauth"
|
||||
name: "default"
|
||||
directory: "/var/opt/nevisauth/default"
|
||||
pid: "systemctl show nevisauth@default -p MainPID | cut -d '=' -f2"
|
||||
source:
|
||||
url: "/nevisadmin/#/projects/DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT/patterns/6ec6739e824c8e56d9633622"
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "6ec6739e824c8e56d9633622"
|
||||
patternClass: "ch.nevis.admin.v4.plugin.nevisauth.patterns.NevisAuthDeployable"
|
||||
resources:
|
||||
ports:
|
||||
- "0.0.0.0:8991"
|
||||
control:
|
||||
start: "systemctl restart nevisauth@default &"
|
||||
stop: "systemctl stop nevisauth@default"
|
||||
status: "systemctl status nevisauth@default"
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDAzCCAesCFFJeJMyjfQq6To7dqRpfc5oDX//oMA0GCSqGSIb3DQEBCwUAMD4x
|
||||
CzAJBgNVBAYTAkNIMQ0wCwYDVQQHDARCZXJuMRAwDgYDVQQKDAdBZG5vdnVtMQ4w
|
||||
DAYDVQQDDAVjb3NzYTAeFw0yNDExMDcxMzA3NTJaFw0yNTExMDcxMzA3NTJaMD4x
|
||||
CzAJBgNVBAYTAkNIMQ0wCwYDVQQHDARCZXJuMRAwDgYDVQQKDAdBZG5vdnVtMQ4w
|
||||
DAYDVQQDDAVjb3NzYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOWq
|
||||
kbFj7UAPDlzgqua7/ws43sIswHuhA400R3jKkHcvTC/QduDuDWoTeYkiQdv8lo7k
|
||||
PVQA7tP//Pe+2O2wS0spNMrlw0Jv0MWeGlN1Jv5PH9TuOnL1nrd6w2OCKClnR7t3
|
||||
xWZUEd3t4AtM/69VKNwSvVADt5yU6tifj1vCiE4uPoDkI8TNbT92aL2aDnq+VVRL
|
||||
Eki5SpQ6ZGUlZGFGhMMOoA9efnTGqrJBsP3m50SAEUqTUpo1aH6IaJXorf8+zzEE
|
||||
zWD4A13b0kvz75A9qh1rReYZgr1sQIfWwzoP76HQwpdEQsjnAteJi1SV7rSK/ekQ
|
||||
+iuJ9ql2Mjpve8vsWrsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAE4GJgtYJRI3r
|
||||
hAWb4ptd6ngPfvW6pNb8Q9do/k0yVmAzckibRVSgmCoTeFnArn56NB2nrZlpnncG
|
||||
IYB5uVI7jiEHCTZRXG7JbI/MHiwkq5P+Mf+OlvJWgiWkKFteJS46GBVo6JFVAIbv
|
||||
H/UEflHbeTbaSYsoH0Xv54S9IvIuv/IA7KooFRmuzRb10hBBV0EUdrGfdBHmSpQf
|
||||
CiSMPOTqu3nzcms4O4DLHnW2kVRyrd/G0Lkg/FUGsN4ZHyYGSC36gUOFqubGy9GV
|
||||
HJqM6758pE0Myk7LMFGd8MrlnYTeWgBnRlAmfzMVMWZaAaRGGZ5SwQrZmenVB1ne
|
||||
Ix41vfiHmA==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,30 @@
|
|||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIIFKzBVBgkqhkiG9w0BBQ0wSDAnBgkqhkiG9w0BBQwwGgQU1w+0ZNamq3X91Bur
|
||||
d/pQZH7Yq+wCAggAMB0GCWCGSAFlAwQBKgQQ+NvXoFb05UDnOc75yNr/DQSCBNBD
|
||||
m3o2GoHnMupzBuc4G7KdllLa2iaP0zkbK5SrJ2QlBWzei3K9PfyWsCSiGDoEL1fG
|
||||
X8sjFmWDXiKg8z2KjZ8SAIVliMusz318hIjSkv7MOtcEKZ7uZ3C9e/AHl2CY55O7
|
||||
OroDbvQfiexVyKS49vd8lex9DFZQ6nrhK7sQoygDeo469NoCJKu4s1Q657L8aeoQ
|
||||
RSYBgtO3tThXIHqrW7FhuYGy8GSi8nI5axGTE7tfo3NqQo4Ap44a9W+vP8NsWUm+
|
||||
WAyoErFTWxiuzXDU8LV2ziN4IZLsMZ0dgJjElIY6lAopr9YtjmKV+3IofYcy9BDa
|
||||
q1WDfwqBBdoKdbMGLvXs8MBTPWbTinbiLGjNfRehtTdQ6zoVBYdxbPQBbw39vH2q
|
||||
k2eLYsWuJmWQHfd3vCEc1B5kAgdAIiPSKvY5wRqb2cg1V/MjQRVZy7wHUBcYVx14
|
||||
aXPqOgvkSLXYN3nt+X3PsubU5l/aOM9KCI2gT8j4AtvBjgVWzKglyVe5l0T8FGQ4
|
||||
KMykvMwFQ2x6g63GyF+xfIM9XMVo8EJ7XNafz18CJ2s7HZ7Zv6twM2D2+xTaa9iq
|
||||
CTcShTPVOLmnLfz6/3I7KKFMKOtm05rZTW7P3dOwcO8symm8qsfz5Kb4FC0H1Kmj
|
||||
MAPO3vAhCIkHOsvrQiFP5RiIk2C+Ea1ygmSl5L3VC2eVA7Hy5FCDiZg7qJAWZqCH
|
||||
ik/cttmzTKy44x2BvrPIKy5l37uGHFcRG/AgIY8hXo+1LUjeKpjjE9hs6MXSqyZh
|
||||
zTkWFOXUkWFeBHrZqR45WO+ByVx7qD55pQmo2YMQ1q7fzM3VCzpO1OD1HAdhGWRu
|
||||
3cin9Asj3+0X4SUknf/ZrVPGOdyj5Lj09ymL05hwYsiuAtWX3hH4I4ZYB6miJ3+F
|
||||
rOnIPed/exXuCsq/H0+WWLilwDxO7VOamO3ggDKm0LebwTx+N8HQaKVXl7HYh5F8
|
||||
Jmpp/bQrFswa42874GaZMJimyQx66SDsPTULjlk+0Ydc2gbWqvIbzBzerxlIm9nR
|
||||
vWsx+V4+3Zvoom5Vwuo3P7Su3DAY142pn6cmqn77TSaMapWLK23tk24D7wUt6ROY
|
||||
xRPfVuSgQ+u3xxDKmKLfFvdFbVx2YNmd174fQJFvr5cpwpMg/uqvnXdw6YwJugmd
|
||||
hGtsodhd3aoX/BjTxkur2Aw4GFpXQYCdeioVFaOIPZDsmOokNcdVw45qC8xRn2Bx
|
||||
VcZ24opYyGtnfl5DxOd2tl1dsKKxoVR4nWphHED//08ZfcUWaudeyIMfNJm790s7
|
||||
8+0smUkQpd2f1Xc3YNBKwLN+U6hUme9DvSBoF6bQn/LEBGOSrE546W/Refytg907
|
||||
SdHN1Qv5uDpFH7iIr44o/uIbwg0S1l/uLGNUR2o+ORt5SXh4fY6spjgCZc+UleUU
|
||||
hjiGE8Nqh5fSvpnud7p2KPSUhuKcybQbKmn/mTBbP2GNaL2nHHXGRHO7MXGhZpHB
|
||||
njmd8DyE+SOBtQTB+aLYlPGkRTNbFkJdyzc9gMCUJCEQs9CNrj/VGy4ZM41Khi3z
|
||||
ZG2hJNjINqaZ5mzDJCdAHuNiwsvdhNqYJVVgPXl3ZkjcBbarZuAzxSih7FlEGDRs
|
||||
43vyCSLp33X5o2gLVf9FsFRZgQxwZnJvaWl86OUWeA==
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
echo '09I1B4lsP4+KQB8dB3AeEyU4RawLttIo55+0EWPPh0I='
|
|
@ -0,0 +1,50 @@
|
|||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIIFKzBVBgkqhkiG9w0BBQ0wSDAnBgkqhkiG9w0BBQwwGgQU1w+0ZNamq3X91Bur
|
||||
d/pQZH7Yq+wCAggAMB0GCWCGSAFlAwQBKgQQ+NvXoFb05UDnOc75yNr/DQSCBNBD
|
||||
m3o2GoHnMupzBuc4G7KdllLa2iaP0zkbK5SrJ2QlBWzei3K9PfyWsCSiGDoEL1fG
|
||||
X8sjFmWDXiKg8z2KjZ8SAIVliMusz318hIjSkv7MOtcEKZ7uZ3C9e/AHl2CY55O7
|
||||
OroDbvQfiexVyKS49vd8lex9DFZQ6nrhK7sQoygDeo469NoCJKu4s1Q657L8aeoQ
|
||||
RSYBgtO3tThXIHqrW7FhuYGy8GSi8nI5axGTE7tfo3NqQo4Ap44a9W+vP8NsWUm+
|
||||
WAyoErFTWxiuzXDU8LV2ziN4IZLsMZ0dgJjElIY6lAopr9YtjmKV+3IofYcy9BDa
|
||||
q1WDfwqBBdoKdbMGLvXs8MBTPWbTinbiLGjNfRehtTdQ6zoVBYdxbPQBbw39vH2q
|
||||
k2eLYsWuJmWQHfd3vCEc1B5kAgdAIiPSKvY5wRqb2cg1V/MjQRVZy7wHUBcYVx14
|
||||
aXPqOgvkSLXYN3nt+X3PsubU5l/aOM9KCI2gT8j4AtvBjgVWzKglyVe5l0T8FGQ4
|
||||
KMykvMwFQ2x6g63GyF+xfIM9XMVo8EJ7XNafz18CJ2s7HZ7Zv6twM2D2+xTaa9iq
|
||||
CTcShTPVOLmnLfz6/3I7KKFMKOtm05rZTW7P3dOwcO8symm8qsfz5Kb4FC0H1Kmj
|
||||
MAPO3vAhCIkHOsvrQiFP5RiIk2C+Ea1ygmSl5L3VC2eVA7Hy5FCDiZg7qJAWZqCH
|
||||
ik/cttmzTKy44x2BvrPIKy5l37uGHFcRG/AgIY8hXo+1LUjeKpjjE9hs6MXSqyZh
|
||||
zTkWFOXUkWFeBHrZqR45WO+ByVx7qD55pQmo2YMQ1q7fzM3VCzpO1OD1HAdhGWRu
|
||||
3cin9Asj3+0X4SUknf/ZrVPGOdyj5Lj09ymL05hwYsiuAtWX3hH4I4ZYB6miJ3+F
|
||||
rOnIPed/exXuCsq/H0+WWLilwDxO7VOamO3ggDKm0LebwTx+N8HQaKVXl7HYh5F8
|
||||
Jmpp/bQrFswa42874GaZMJimyQx66SDsPTULjlk+0Ydc2gbWqvIbzBzerxlIm9nR
|
||||
vWsx+V4+3Zvoom5Vwuo3P7Su3DAY142pn6cmqn77TSaMapWLK23tk24D7wUt6ROY
|
||||
xRPfVuSgQ+u3xxDKmKLfFvdFbVx2YNmd174fQJFvr5cpwpMg/uqvnXdw6YwJugmd
|
||||
hGtsodhd3aoX/BjTxkur2Aw4GFpXQYCdeioVFaOIPZDsmOokNcdVw45qC8xRn2Bx
|
||||
VcZ24opYyGtnfl5DxOd2tl1dsKKxoVR4nWphHED//08ZfcUWaudeyIMfNJm790s7
|
||||
8+0smUkQpd2f1Xc3YNBKwLN+U6hUme9DvSBoF6bQn/LEBGOSrE546W/Refytg907
|
||||
SdHN1Qv5uDpFH7iIr44o/uIbwg0S1l/uLGNUR2o+ORt5SXh4fY6spjgCZc+UleUU
|
||||
hjiGE8Nqh5fSvpnud7p2KPSUhuKcybQbKmn/mTBbP2GNaL2nHHXGRHO7MXGhZpHB
|
||||
njmd8DyE+SOBtQTB+aLYlPGkRTNbFkJdyzc9gMCUJCEQs9CNrj/VGy4ZM41Khi3z
|
||||
ZG2hJNjINqaZ5mzDJCdAHuNiwsvdhNqYJVVgPXl3ZkjcBbarZuAzxSih7FlEGDRs
|
||||
43vyCSLp33X5o2gLVf9FsFRZgQxwZnJvaWl86OUWeA==
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDAzCCAesCFFJeJMyjfQq6To7dqRpfc5oDX//oMA0GCSqGSIb3DQEBCwUAMD4x
|
||||
CzAJBgNVBAYTAkNIMQ0wCwYDVQQHDARCZXJuMRAwDgYDVQQKDAdBZG5vdnVtMQ4w
|
||||
DAYDVQQDDAVjb3NzYTAeFw0yNDExMDcxMzA3NTJaFw0yNTExMDcxMzA3NTJaMD4x
|
||||
CzAJBgNVBAYTAkNIMQ0wCwYDVQQHDARCZXJuMRAwDgYDVQQKDAdBZG5vdnVtMQ4w
|
||||
DAYDVQQDDAVjb3NzYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOWq
|
||||
kbFj7UAPDlzgqua7/ws43sIswHuhA400R3jKkHcvTC/QduDuDWoTeYkiQdv8lo7k
|
||||
PVQA7tP//Pe+2O2wS0spNMrlw0Jv0MWeGlN1Jv5PH9TuOnL1nrd6w2OCKClnR7t3
|
||||
xWZUEd3t4AtM/69VKNwSvVADt5yU6tifj1vCiE4uPoDkI8TNbT92aL2aDnq+VVRL
|
||||
Eki5SpQ6ZGUlZGFGhMMOoA9efnTGqrJBsP3m50SAEUqTUpo1aH6IaJXorf8+zzEE
|
||||
zWD4A13b0kvz75A9qh1rReYZgr1sQIfWwzoP76HQwpdEQsjnAteJi1SV7rSK/ekQ
|
||||
+iuJ9ql2Mjpve8vsWrsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAE4GJgtYJRI3r
|
||||
hAWb4ptd6ngPfvW6pNb8Q9do/k0yVmAzckibRVSgmCoTeFnArn56NB2nrZlpnncG
|
||||
IYB5uVI7jiEHCTZRXG7JbI/MHiwkq5P+Mf+OlvJWgiWkKFteJS46GBVo6JFVAIbv
|
||||
H/UEflHbeTbaSYsoH0Xv54S9IvIuv/IA7KooFRmuzRb10hBBV0EUdrGfdBHmSpQf
|
||||
CiSMPOTqu3nzcms4O4DLHnW2kVRyrd/G0Lkg/FUGsN4ZHyYGSC36gUOFqubGy9GV
|
||||
HJqM6758pE0Myk7LMFGd8MrlnYTeWgBnRlAmfzMVMWZaAaRGGZ5SwQrZmenVB1ne
|
||||
Ix41vfiHmA==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
accept.button.label=Accept
|
||||
cancel.button.label=Cancel
|
||||
continue.button.label=Continue
|
||||
deputy.profile.label=(Deputy Profile)
|
||||
error.saml.failed=Please close your browser and try again.
|
||||
error_1=Please check your input.
|
||||
error_10=Please select the correct user account.
|
||||
error_100=Certificate upload not possible. Certificate already exists. Please contact your helpdesk.
|
||||
error_101=The entered email address is not valid.
|
||||
error_11=Please use another certficate or login with another credential type.
|
||||
error_2=Please select another login name.
|
||||
error_3=Your account will be locked if next authentication fails.
|
||||
error_4=Your new password does not comply with the security policy. Please choose a different password.
|
||||
error_5=Error in password confirmation.
|
||||
error_50=The new password is too short.
|
||||
error_55=The new password has to differ from old passwords.
|
||||
error_6=Password change required.
|
||||
error_7=Change of login ID required.
|
||||
error_8=Your account has been locked due to repeated authentication failures.
|
||||
error_81=No access card found, access from internet denied.
|
||||
error_83=Your access card is no longer valid. Please contact your advisor to get a new access card.
|
||||
error_9=Session take over failed.
|
||||
error_97=You are not authorized to access this resource.
|
||||
error_98=Your account has been locked.
|
||||
error_99=System problems. Please try later.
|
||||
info.logout.confirmation=Please confirm that you want to log out.
|
||||
info.logout.reminder=Your session on this application has expired. Try again with a login.
|
||||
info.oauth.consent=Do you want to authorise this application to access your data?
|
||||
info.timeout.page=Your session on this application has expired. Try again with a login.
|
||||
login.button.label=Login
|
||||
logout.label=Logout
|
||||
logout.text=You have successfully logged out.
|
||||
method.certificate.label=Certificate
|
||||
method.fido.label=Mobile Authentication
|
||||
method.fido2.label=FIDO 2
|
||||
method.mtan.label=mTAN Code
|
||||
method.oath.label=OATH Authenticator App
|
||||
method.otp.label=OTP (One-Time Password)
|
||||
method.recovery.label=Recovery Codes
|
||||
method.safeword.label=SafeWord
|
||||
method.securid.label=SecurID
|
||||
method.ticket.label=Ticket
|
||||
outarg.lastLogin.never=Never
|
||||
policyFailure.dictionary=▪ must not be taken from a dictionary.
|
||||
policyFailure.history.History=▪ must be different from previously selected passwords.
|
||||
policyFailure.regex.control=▪ cannot contain more than {0} control characters.
|
||||
policyFailure.regex.lower=▪ must contain at least {0} lower case characters.
|
||||
policyFailure.regex.maxCharacterRepetitions=▪ characters must not occur more than {0} time(s) consecutively.
|
||||
policyFailure.regex.maxLength=▪ must be at most {0} characters long.
|
||||
policyFailure.regex.minLength=▪ must be at least {0} characters long.
|
||||
policyFailure.regex.nonAlnum=▪ must contain at least {0} non-alphanumeric characters.
|
||||
policyFailure.regex.nonAscii=▪ cannot contain more than {0} non-ASCII characters.
|
||||
policyFailure.regex.nonGraph=▪ cannot contain more than {0} non-printable characters.
|
||||
policyFailure.regex.nonLetter=▪ must contain at least {0} non-letter characters.
|
||||
policyFailure.regex.numeric=▪ must contain at least {0} numeric characters.
|
||||
policyFailure.regex.upper=▪ must contain at least {0} upper case characters.
|
||||
policyInfo.dictionary=▪ must not be taken from a dictionary.
|
||||
policyInfo.history.History=▪ must be different from previously selected passwords.
|
||||
policyInfo.regex.control=▪ cannot contain more than {0} control characters.
|
||||
policyInfo.regex.lower=▪ must contain at least {0} lower case characters.
|
||||
policyInfo.regex.maxCharacterRepetitions=▪ characters must not occur more than {0} time(s) consecutively.
|
||||
policyInfo.regex.maxLength=▪ must be at most {0} characters long.
|
||||
policyInfo.regex.minLength=▪ must be at least {0} characters long.
|
||||
policyInfo.regex.nonAlnum=▪ must contain at least {0} non-alphanumeric characters.
|
||||
policyInfo.regex.nonAscii=▪ cannot contain more than {0} non-ASCII characters.
|
||||
policyInfo.regex.nonGraph=▪ cannot contain more than {0} non-printable characters.
|
||||
policyInfo.regex.nonLetter=▪ must contain at least {0} non-letter characters.
|
||||
policyInfo.regex.numeric=▪ must contain at least {0} numeric characters.
|
||||
policyInfo.regex.upper=▪ must contain at least {0} upper case characters.
|
||||
policyInfo.title=The password has to comply with the following password policy:
|
||||
reject.button.label=Deny
|
||||
submit.button.label=Submit
|
||||
tan.sent=Please enter the security code which has been sent to your mobile phone.
|
||||
title.logout=Logout
|
||||
title.logout.confirmation=Logout
|
||||
title.logout.reminder=Logout
|
||||
title.oauth.consent=Client Authorization
|
||||
title.saml.failed=Error
|
||||
title.timeout.page=Logout
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
accept.button.label=Akzeptieren
|
||||
cancel.button.label=Abbrechen
|
||||
continue.button.label=Weiter
|
||||
deputy.profile.label=(Profil Stellvertreter)
|
||||
error.saml.failed=Bitte schliessen Sie Ihren Browser und versuchen Sie es erneut.
|
||||
error_1=Bitte überprüfen Sie Ihre Eingabe.
|
||||
error_10=Bitte wählen Sie den gewünschten Benutzer.
|
||||
error_100=Zertifikat-Upload nicht möglich. Zertifikat bereits vorhanden. Bitte kontaktieren Sie Ihren Helpdesk.
|
||||
error_101=Die angegebene E-Mail Adresse ist ungültig.
|
||||
error_11=Bitte verwenden Sie ein anderes Zertifikat oder ein alternatives Authentisierungsmittel.
|
||||
error_2=Bitte wählen Sie einen anderen Login-Namen.
|
||||
error_3=Falls Ihr nächster Login fehlschlägt, wird Ihr Konto gesperrt.
|
||||
error_4=Ihr neues Passwort wurde nicht akzeptiert. Bitte wählen Sie eines, das den Passwortvorgaben entspricht.
|
||||
error_5=Die Eingabe zur Bestätigung des Passwortes ist falsch.
|
||||
error_50=Das neue Passwort ist zu kurz.
|
||||
error_55=Das neue Passwort muss sich von alten Passwörtern unterscheiden.
|
||||
error_6=Passwortwechsel erforderlich.
|
||||
error_7=Wechsel der Login-ID erforderlich.
|
||||
error_8=Ihr Konto wurde infolge wiederholt fehlgeschlagener Authentisierung gesperrt.
|
||||
error_81=Keine Rasterkarte gefunden, Zugang vom Internet verweigert.
|
||||
error_83=Ihre Rasterkarte ist aufgebraucht. Bitte kontaktieren Sie Ihren Berater, um eine neue zu erhalten.
|
||||
error_9=Die SSO-Session konnte nicht übernommen werden.
|
||||
error_97=Sie verfügen nicht über die für den Zugriff auf diese Ressource benötigte Berechtigung.
|
||||
error_98=Ihr Konto ist gesperrt.
|
||||
error_99=Systemfehler. Bitte versuchen Sie es später.
|
||||
info.logout.confirmation=Bitte bestätigen Sie, dass Sie sich abmelden möchten.
|
||||
info.logout.reminder=Ihre Session ist auf dieser Applikation abgelaufen. Versuchen Sie es nochmals mit einem Login.
|
||||
info.oauth.consent=Wollen Sie der Anwendung den Zugriff erlauben?
|
||||
info.timeout.page=Ihre Session ist auf dieser Applikation abgelaufen. Versuchen Sie es nochmals mit einem Login.
|
||||
login.button.label=Login
|
||||
logout.label=Logout
|
||||
logout.text=Sie haben sich erfolgreich abgemeldet.
|
||||
method.certificate.label=Zertifikat
|
||||
method.fido.label=Mobile Authentication
|
||||
method.fido2.label=FIDO 2
|
||||
method.mtan.label=mTAN-Code
|
||||
method.oath.label=OATH Authenticator-App
|
||||
method.otp.label=OTP (One-Time Passwort)
|
||||
method.recovery.label=Wiederherstellungscodes
|
||||
method.safeword.label=SafeWord
|
||||
method.securid.label=SecurID
|
||||
method.ticket.label=Ticket
|
||||
outarg.lastLogin.never=Nie
|
||||
policyFailure.dictionary=▪ darf nicht aus einem Wörterbuch stammen.
|
||||
policyFailure.history.History=▪ muss sich von vorhergehenden Passwörtern unterscheiden.
|
||||
policyFailure.regex.control=▪ darf höchstens {0} Kontrollzeichen enthalten.
|
||||
policyFailure.regex.lower=▪ muss {0} Kleinbuchstaben enthalten.
|
||||
policyFailure.regex.maxCharacterRepetitions=▪ darf nicht eine Sequenz länger als {0} des gleichen Zeichens enthalten.
|
||||
policyFailure.regex.maxLength=Länge des Passwortes darf höchstens {0} sein.
|
||||
policyFailure.regex.minLength=Länge des Passwortes muss mindestens {0} sein.
|
||||
policyFailure.regex.nonAlnum=▪ muss {0} nicht-alphanumerische Zeichen enthalten.
|
||||
policyFailure.regex.nonAscii=▪ darf höchstens {0} Zeichen ausserhalb des ASCII-Zeichensatzes enthalten.
|
||||
policyFailure.regex.nonGraph=▪ darf höchstens {0} nicht-druckende Zeichen enthalten.
|
||||
policyFailure.regex.nonLetter=▪ muss {0} Zeichen enthalten, die keine Buchstaben sind.
|
||||
policyFailure.regex.numeric=▪ muss {0} numerische Zeichen enthalten.
|
||||
policyFailure.regex.upper=▪ muss {0} Grossbuchstaben enthalten.
|
||||
policyInfo.dictionary=▪ darf nicht aus einem Wörterbuch stammen.
|
||||
policyInfo.history.History=▪ darf keines der zuletzt verwendeten Passwörtern sein.
|
||||
policyInfo.regex.control=▪ darf höchstens {0} Kontrollzeichen enthalten.
|
||||
policyInfo.regex.lower=▪ muss mindestens {0} Kleinbuchstaben enthalten.
|
||||
policyInfo.regex.maxCharacterRepetitions=▪ darf nicht eine Sequenz länger als {0} des gleichen Zeichens enthalten.
|
||||
policyInfo.regex.maxLength=▪ darf höchstens {0} Zeichen enthalten.
|
||||
policyInfo.regex.minLength=▪ muss mindestens {0} Zeichen enthalten.
|
||||
policyInfo.regex.nonAlnum=▪ muss mindestens {0} Zeichen enthalten, die nicht Alphanumerisch sind.
|
||||
policyInfo.regex.nonAscii=▪ darf höchstens {0} Zeichen ausserhalb des ASCII-Zeichensatzes enthalten.
|
||||
policyInfo.regex.nonGraph=▪ darf höchstens {0} nicht-druckende Zeichen enthalten.
|
||||
policyInfo.regex.nonLetter=▪ muss mindestens {0} Zeichen enthalten, die keine Buchstaben sind.
|
||||
policyInfo.regex.numeric=▪ muss mindestens {0} numerische Zeichen enthalten.
|
||||
policyInfo.regex.upper=▪ muss mindestens {0} Grossbuchstaben enthalten.
|
||||
policyInfo.title=Das Passwort muss den folgenden Passwort-Richtlinien entsprechen:
|
||||
reject.button.label=Ablehnen
|
||||
submit.button.label=Senden
|
||||
tan.sent=Bitte erfassen Sie den Sicherheitscode, welcher an Ihr Mobiltelefon gesendet wurde.
|
||||
title.logout=Logout
|
||||
title.logout.confirmation=Logout
|
||||
title.logout.reminder=Logout
|
||||
title.oauth.consent=Client Authorisierung
|
||||
title.saml.failed=Error
|
||||
title.timeout.page=Logout
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
accept.button.label=Accept
|
||||
cancel.button.label=Cancel
|
||||
continue.button.label=Continue
|
||||
deputy.profile.label=(Deputy Profile)
|
||||
error.saml.failed=Please close your browser and try again.
|
||||
error_1=Please check your input.
|
||||
error_10=Please select the correct user account.
|
||||
error_100=Certificate upload not possible. Certificate already exists. Please contact your helpdesk.
|
||||
error_101=The entered email address is not valid.
|
||||
error_11=Please use another certficate or login with another credential type.
|
||||
error_2=Please select another login name.
|
||||
error_3=Your account will be locked if next authentication fails.
|
||||
error_4=Your new password does not comply with the security policy. Please choose a different password.
|
||||
error_5=Error in password confirmation.
|
||||
error_50=The new password is too short.
|
||||
error_55=The new password has to differ from old passwords.
|
||||
error_6=Password change required.
|
||||
error_7=Change of login ID required.
|
||||
error_8=Your account has been locked due to repeated authentication failures.
|
||||
error_81=No access card found, access from internet denied.
|
||||
error_83=Your access card is no longer valid. Please contact your advisor to get a new access card.
|
||||
error_9=Session take over failed.
|
||||
error_97=You are not authorized to access this resource.
|
||||
error_98=Your account has been locked.
|
||||
error_99=System problems. Please try later.
|
||||
info.logout.confirmation=Please confirm that you want to log out.
|
||||
info.logout.reminder=Your session on this application has expired. Try again with a login.
|
||||
info.oauth.consent=Do you want to authorise this application to access your data?
|
||||
info.timeout.page=Your session on this application has expired. Try again with a login.
|
||||
login.button.label=Login
|
||||
logout.label=Logout
|
||||
logout.text=You have successfully logged out.
|
||||
method.certificate.label=Certificate
|
||||
method.fido.label=Mobile Authentication
|
||||
method.fido2.label=FIDO 2
|
||||
method.mtan.label=mTAN Code
|
||||
method.oath.label=OATH Authenticator App
|
||||
method.otp.label=OTP (One-Time Password)
|
||||
method.recovery.label=Recovery Codes
|
||||
method.safeword.label=SafeWord
|
||||
method.securid.label=SecurID
|
||||
method.ticket.label=Ticket
|
||||
outarg.lastLogin.never=Never
|
||||
policyFailure.dictionary=▪ must not be taken from a dictionary.
|
||||
policyFailure.history.History=▪ must be different from previously selected passwords.
|
||||
policyFailure.regex.control=▪ cannot contain more than {0} control characters.
|
||||
policyFailure.regex.lower=▪ must contain at least {0} lower case characters.
|
||||
policyFailure.regex.maxCharacterRepetitions=▪ characters must not occur more than {0} time(s) consecutively.
|
||||
policyFailure.regex.maxLength=▪ must be at most {0} characters long.
|
||||
policyFailure.regex.minLength=▪ must be at least {0} characters long.
|
||||
policyFailure.regex.nonAlnum=▪ must contain at least {0} non-alphanumeric characters.
|
||||
policyFailure.regex.nonAscii=▪ cannot contain more than {0} non-ASCII characters.
|
||||
policyFailure.regex.nonGraph=▪ cannot contain more than {0} non-printable characters.
|
||||
policyFailure.regex.nonLetter=▪ must contain at least {0} non-letter characters.
|
||||
policyFailure.regex.numeric=▪ must contain at least {0} numeric characters.
|
||||
policyFailure.regex.upper=▪ must contain at least {0} upper case characters.
|
||||
policyInfo.dictionary=▪ must not be taken from a dictionary.
|
||||
policyInfo.history.History=▪ must be different from previously selected passwords.
|
||||
policyInfo.regex.control=▪ cannot contain more than {0} control characters.
|
||||
policyInfo.regex.lower=▪ must contain at least {0} lower case characters.
|
||||
policyInfo.regex.maxCharacterRepetitions=▪ characters must not occur more than {0} time(s) consecutively.
|
||||
policyInfo.regex.maxLength=▪ must be at most {0} characters long.
|
||||
policyInfo.regex.minLength=▪ must be at least {0} characters long.
|
||||
policyInfo.regex.nonAlnum=▪ must contain at least {0} non-alphanumeric characters.
|
||||
policyInfo.regex.nonAscii=▪ cannot contain more than {0} non-ASCII characters.
|
||||
policyInfo.regex.nonGraph=▪ cannot contain more than {0} non-printable characters.
|
||||
policyInfo.regex.nonLetter=▪ must contain at least {0} non-letter characters.
|
||||
policyInfo.regex.numeric=▪ must contain at least {0} numeric characters.
|
||||
policyInfo.regex.upper=▪ must contain at least {0} upper case characters.
|
||||
policyInfo.title=The password has to comply with the following password policy:
|
||||
reject.button.label=Deny
|
||||
submit.button.label=Submit
|
||||
tan.sent=Please enter the security code which has been sent to your mobile phone.
|
||||
title.logout=Logout
|
||||
title.logout.confirmation=Logout
|
||||
title.logout.reminder=Logout
|
||||
title.oauth.consent=Client Authorization
|
||||
title.saml.failed=Error
|
||||
title.timeout.page=Logout
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
accept.button.label=Accepter
|
||||
cancel.button.label=Abandonner
|
||||
continue.button.label=Continuer
|
||||
deputy.profile.label=(Profil du suppléant)
|
||||
error.saml.failed=Fermez votre navigateur et r;eacute;essayez.
|
||||
error_1=Veuillez vérifier vos données, s.v.p.
|
||||
error_10=Choisissez votre compte.
|
||||
error_100=Téléchargement du certificat pas possible. Certificat existe déjà. Veuillez contacter le helpdesk s.v.p.
|
||||
error_101=L'adresse e-mail é n'est pas valide.
|
||||
error_11=Choisissez un autre certificat, s.v.p.
|
||||
error_2=Choisissez un autre nom, s.v.p.
|
||||
error_3=Si l'authentification ne réussit pas au prochain essai, votre compte sera bloqué.
|
||||
error_4=Votre nouveau mot de passe ne conforme pas aux mesures de sécurité
|
||||
error_5=Votre confirmation du mot de passe ne correspond pas au mot de passe donné.
|
||||
error_50=Le nouveau mot de passe est trop court.
|
||||
error_55=Le nouveau mot de passe doit différer de l'ancien.
|
||||
error_6=Veuillez changer votre mot de passe, s.v.p.
|
||||
error_7=Veuillez changer votre login ID, s.v.p.
|
||||
error_8=Votre compte n'est pas active.
|
||||
error_81=Pas d'access card trouvé, l'accès par l'internet est refusé.
|
||||
error_83=Votre access card n'est plus valable, veuillez contacter votre gestionnaire.
|
||||
error_9=Il n'est pas possible de transmettre la session.
|
||||
error_97=Vous n'avez pas les autorisations nécessaires pour accéder à cette ressource.
|
||||
error_98=Votre compte a été bloqué.
|
||||
error_99=Problème technique. Veuillez essayer plus tard, s.v.p.
|
||||
info.logout.confirmation=Veuillez confirmer que vous souhaitez vous déconnecter.
|
||||
info.logout.reminder=Votre session sur cette application a expirée. Essayez encore avec un login.
|
||||
info.oauth.consent=Voulez-vous autoriser l'application?
|
||||
info.timeout.page=Votre session sur cette application a expirée. Essayez encore avec un login.
|
||||
login.button.label=Login
|
||||
logout.label=Logout
|
||||
logout.text=Au revoir
|
||||
method.certificate.label=Certificat
|
||||
method.fido.label=Mobile Authentication
|
||||
method.fido2.label=FIDO 2
|
||||
method.mtan.label=Code mTAN
|
||||
method.oath.label=Application d'authentification OATH
|
||||
method.otp.label=OTP (One-Time Password)
|
||||
method.recovery.label=Codes de récupération
|
||||
method.safeword.label=SafeWord
|
||||
method.securid.label=SecurID
|
||||
method.ticket.label=Ticket
|
||||
outarg.lastLogin.never=Jamais
|
||||
policyFailure.dictionary=▪ ne peut pas être pris d'un dictionnaire.
|
||||
policyFailure.history.History=▪ doit être différent des mots de passe préalablement sélectionnés.
|
||||
policyFailure.regex.control=▪ ne peut contenir plus de {0} caractères de commande.
|
||||
policyFailure.regex.lower=▪ doit contenir au moins {0} caractère(s) minuscule(s).
|
||||
policyFailure.regex.maxCharacterRepetitions=▪ ne peut contenir une séquence de plus de {0} du même caractère.
|
||||
policyFailure.regex.maxLength=La longueur doit être d'au plus {0}.
|
||||
policyFailure.regex.minLength=La longueur doit être d'au moins {0}.
|
||||
policyFailure.regex.nonAlnum=▪ doit contenir au moins {0} caractères non alphanumériques.
|
||||
policyFailure.regex.nonAscii=▪ ne peut contenir plus de {0} caractères non ASCII ({1}).
|
||||
policyFailure.regex.nonGraph=▪ ne peut contenir plus de {0} caractères non imprimables ({1}).
|
||||
policyFailure.regex.nonLetter=▪ doit contenir au moins {0} caractères qui ne sont pas des lettres.
|
||||
policyFailure.regex.numeric=▪ doit comprendre {0} caractères numériques.
|
||||
policyFailure.regex.upper=▪ doit contenir au moins {0} caractère(s) majuscule(s).
|
||||
policyInfo.dictionary=▪ ne peut pas être pris d'un dictionnaire.
|
||||
policyInfo.history.History=▪ ne peut pas être l' précédemment choisis.
|
||||
policyInfo.regex.control=▪ ne peut contenir plus de {0} caractères de commande.
|
||||
policyInfo.regex.lower=▪ doit contenir au moins {0} caractère(s) minuscule(s).
|
||||
policyInfo.regex.maxCharacterRepetitions=▪ ne peut contenir une séquence de plus de {0} du même caractère.
|
||||
policyInfo.regex.maxLength=▪ la longueur doit être d'au plus {0}.
|
||||
policyInfo.regex.minLength=▪ la longueur doit être d'au moins {0}.
|
||||
policyInfo.regex.nonAlnum=▪ doit contenir au moins {0} caractères non alphanumériques.
|
||||
policyInfo.regex.nonAscii=▪ ne peut contenir plus de {0} caractères non ASCII.
|
||||
policyInfo.regex.nonGraph=▪ ne peut contenir plus de {0} caractères non imprimables.
|
||||
policyInfo.regex.nonLetter=▪ doit contenir au moins {0} caractères qui ne sont pas des lettres.
|
||||
policyInfo.regex.numeric=▪ doit comprendre au minimum {0} caractères numériques.
|
||||
policyInfo.regex.upper=▪ doit contenir au moins {0} caractère(s) majuscule(s).
|
||||
policyInfo.title=Le mot de passe doit respecter les règles suivantes:
|
||||
reject.button.label=Refuser
|
||||
submit.button.label=Envoyer
|
||||
tan.sent=Veuillez saisir le code de sécurité que vous avez reçu au votre téléphone mobile.
|
||||
title.logout=Logout
|
||||
title.logout.confirmation=Logout
|
||||
title.logout.reminder=Logout
|
||||
title.oauth.consent=Autorisation du client
|
||||
title.saml.failed=Error
|
||||
title.timeout.page=Logout
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
accept.button.label=Accettare
|
||||
cancel.button.label=Abortire
|
||||
continue.button.label=Continua
|
||||
deputy.profile.label=(profilo del delegato)
|
||||
error.saml.failed=Chiudi il browser e riprova.
|
||||
error_1=Verificare i dati immessi.
|
||||
error_10=Per favore selezionare il conto utente corretto.
|
||||
error_100=Impossibile caricare il certificato. Questo certificato esiste già. La preghiamo di contattare il Suo help desk.
|
||||
error_101=L'indirizzo e-mail inserito non è valido.
|
||||
error_11=Scegliere un altro certificato.
|
||||
error_2=Per favore scegliere un altro nome.
|
||||
error_3=Il conto verrà bloccato se il prossimo login non andrà a buon fine.
|
||||
error_4=La nuova password non è stata accettata. Scegliere una password che sia conforme ai criteri di password.
|
||||
error_5=La conferma della password è errata.
|
||||
error_50=La nuova password è troppo corta.
|
||||
error_55=La nuova password deve essere diversa dalla vecchia.
|
||||
error_6=È necessario modificare la password.
|
||||
error_7=Set up inizale dell'account per il portale necessario.
|
||||
error_8=L'account è stato bloccato. Rivolgersi al servizio assistenza oppure provare con un altro strumento di autenticazione.
|
||||
error_81=Nessuna carta di accesso trovata, accesso da internet rifiutato.
|
||||
error_83=La sua carta di accesso non è più valida. Per favore contatti il suo assistente per ricevere una nuova carta di accesso.
|
||||
error_9=La sessione non può essere ripresa.
|
||||
error_97=Non si dispone delle autorizzazioni necessarie per accedere a questa risorsa.
|
||||
error_98=L'account è stato bloccato.
|
||||
error_99=Errore di sistema. Riprovare.
|
||||
info.logout.confirmation=Si prega di confermare che si desidera disconnettersi.
|
||||
info.logout.reminder=La sessione su questa applicazione &egrave; scaduta. Prova ancora con un login.
|
||||
info.oauth.consent=Vuoi consentire all'applicazione?
|
||||
info.timeout.page=La sessione su questa applicazione &egrave; scaduta. Prova ancora con un login.
|
||||
login.button.label=Login
|
||||
logout.label=Logout
|
||||
logout.text=È uscito con successo.
|
||||
method.certificate.label=Certificato
|
||||
method.fido.label=Mobile Authentication
|
||||
method.fido2.label=FIDO 2
|
||||
method.mtan.label=Codice mTAN
|
||||
method.oath.label=App di autenticazione OATH
|
||||
method.otp.label=OTP (One-Time Password)
|
||||
method.recovery.label=Codici di ripristino
|
||||
method.safeword.label=SafeWord
|
||||
method.securid.label=SecurID
|
||||
method.ticket.label=Ticket
|
||||
outarg.lastLogin.never=Mai
|
||||
policyFailure.dictionary=▪ non può essere presa da un dizionario.
|
||||
policyFailure.history.History=▪ deve essere diversa da password precedenti.
|
||||
policyFailure.regex.control=▪ non può contenere più di {0} caratteri di controllo.
|
||||
policyFailure.regex.lower=▪ deve conenere almeno {0} caratteri minuscoli.
|
||||
policyFailure.regex.maxCharacterRepetitions=▪ non può contentere una sequenza più lunga di {0} caratteri uguali.
|
||||
policyFailure.regex.maxLength=▪ deve contenere al massimo {0} caratteri.
|
||||
policyFailure.regex.minLength=▪ deve contenere almeno {0} caratteri.
|
||||
policyFailure.regex.nonAlnum=▪ deve conenere almeno {0} caratteri non alfanumerici.
|
||||
policyFailure.regex.nonAscii=▪ non può contenere più di {0} caratteri non ASCII.
|
||||
policyFailure.regex.nonGraph=▪ non può contenere più di {0} caratteri non stampabili.
|
||||
policyFailure.regex.nonLetter=▪ non può contenere più di {0} numeri o caratteri speciali.
|
||||
policyFailure.regex.numeric=▪ deve contenere {0} caratteri numerici.
|
||||
policyFailure.regex.upper=▪ deve conenere almeno {0} caratteri maiuscoli.
|
||||
policyInfo.dictionary=▪ non può essere presa da un dizionario.
|
||||
policyInfo.history.History=▪ deve essere diversa dalle password precedenti.
|
||||
policyInfo.regex.control=▪ non può contenere più di {0} carattere/i di controllo.
|
||||
policyInfo.regex.lower=▪ deve conenere almeno {0} carattere/i minuscolo/i.
|
||||
policyInfo.regex.maxCharacterRepetitions=▪ non può contentere una sequenza più lunga di {0} caratteri uguali.
|
||||
policyInfo.regex.maxLength=▪ deve contenere al massimo {0} carattere/i.
|
||||
policyInfo.regex.minLength=▪ deve contenere almeno {0} carattere/i.
|
||||
policyInfo.regex.nonAlnum=▪ deve conenere almeno {0} carattere/i non alfanumerico/i.
|
||||
policyInfo.regex.nonAscii=▪ non può contenere più di {0} carattere/i non ASCII.
|
||||
policyInfo.regex.nonGraph=▪ non può contenere più di {0} carattere/i non stampabile/i.
|
||||
policyInfo.regex.nonLetter=▪ non può contenere più di {0} numero/i o caratere/i speciale/i.
|
||||
policyInfo.regex.numeric=▪ deve contenere un minimo di {0} carattere/i numerico/i.
|
||||
policyInfo.regex.upper=▪ deve conenere almeno {0} carattere/i maiuscolo/i.
|
||||
policyInfo.title=La password deve rispettare le seguenti direttive:
|
||||
reject.button.label=Rifiuti
|
||||
submit.button.label=Continua
|
||||
tan.sent=Inserisci il codice di sicurezza che è stato inviato al tuo telefono cellulare.
|
||||
title.logout=Logout
|
||||
title.logout.confirmation=Logout
|
||||
title.logout.reminder=Logout
|
||||
title.oauth.consent=Autorizzazione del client
|
||||
title.saml.failed=Error
|
||||
title.timeout.page=Logout
|
|
@ -0,0 +1 @@
|
|||
bc.tracer.TraceIndentFactory=ch.nevis.bc.io.Log4jTraceIndentFactory
|
|
@ -0,0 +1,19 @@
|
|||
RTENV_SECURITY_CHECK=no_shell
|
||||
|
||||
JAVA_OPTS=(
|
||||
"-XX:+UseContainerSupport"
|
||||
"-Dfile.encoding=UTF-8"
|
||||
"-XX:MaxRAMPercentage=80.0"
|
||||
"-Djava.net.preferIPv4Stack=true"
|
||||
"-Djava.net.connectionTimeout=10000"
|
||||
"-Djava.net.readTimeout=15000"
|
||||
"-Dch.nevis.esauth.config=/var/opt/nevisauth/default/conf/esauth4.xml"
|
||||
"-Djava.awt.headless=true"
|
||||
"-javaagent:/opt/agent/opentelemetry-javaagent.jar"
|
||||
"-Dotel.javaagent.logging=application"
|
||||
"-Dotel.javaagent.configuration-file=/var/opt/nevisauth/default/conf/otel.properties"
|
||||
"-Dotel.resource.attributes=service.version=8.2405.2,service.instance.id=$HOSTNAME"
|
||||
"-Djavax.net.ssl.trustStore=/var/opt/keys/trust/nai-default-tls-trust/truststore.p12"
|
||||
"-Djavax.net.ssl.trustStorePassword=\${exec:/var/opt/keys/trust/nai-default-tls-trust/keypass}"
|
||||
)
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# this file is generated by nevisAdmin 4
|
||||
security.provider.10=org.bouncycastle.jce.provider.BouncyCastleProvider
|
|
@ -0,0 +1,171 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE esauth-server SYSTEM "/opt/nevisauth/dtd/esauth4.dtd">
|
||||
<esauth-server instance="nai">
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622, pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<SessionCoordinator sessionInitialInactivityTimeout="600" sessionInactivityTimeout="28800" sessionMaxLifetime="28800" sessionIdPreGenerate="true">
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<LocalSessionStore maxSessions="100000"/>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<TokenAssembler name="DefaultTokenAssembler">
|
||||
<Selector default="true"/>
|
||||
<!-- source: pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<TokenSpec ttl="28800">
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<field src="session" key="ch.nevis.session.sessid" as="sessid"/>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<field src="session" key="ch.nevis.session.userid" as="userid"/>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<field src="session" key="ch.nevis.session.authlevel" as="authLevel"/>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<field src="session" key="ch.nevis.session.esauthid" as="esauthid"/>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<field src="session" key="ch.nevis.session.entryid" as="entryid"/>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<field src="session" key="ch.nevis.session.loginid" as="loginId"/>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<field src="session" key="ch.nevis.session.domain" as="domain"/>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<field src="session" key="ch.nevis.session.secroles" as="roles"/>
|
||||
</TokenSpec>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<Signer key="DefaultSigner"/>
|
||||
</TokenAssembler>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<KeyStore name="DefaultKeyStore">
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<KeyObject name="DefaultSigner" certificate="/var/opt/keys/own/nai-sh4r3d-default-default-signer/cert.pem" privateKey="/var/opt/keys/own/nai-sh4r3d-default-default-signer/keystore.jks" passPhrase="pipe:///var/opt/keys/own/nai-sh4r3d-default-default-signer/keypass"/>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<KeyObject name="DefaultSignerTrust" certificate="/var/opt/keys/trust/nai-default-default-signer-trust/truststore.jks"/>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<KeyObject name="Signer_New_JWT_Token" certificate="/var/opt/keys/own/new-pem-key-store/cert.pem" privateKey="/var/opt/keys/own/new-pem-key-store/keystore.jks" passPhrase="pipe:///var/opt/keys/own/new-pem-key-store/keypass"/>
|
||||
</KeyStore>
|
||||
</SessionCoordinator>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622 -->
|
||||
<LocalOutOfContextDataStore reaperPeriod="60"/>
|
||||
<!-- source: pattern://6ec6739e824c8e56d9633622, pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<AuthEngine useLiteralDictionary="true" literalDictionaryLanguages="en,de,fr,it" inputLanguageCookie="LANG" compatLevel="none" addAutheLevelToSecRoles="true" classPath="/var/opt/nevisauth/default/plugin:/opt/nevisauth/plugin" propagateSession="false">
|
||||
<!-- source: pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<Domain name="cossa_realm" default="false" inactiveInterval="7200" reauthInterval="0" resetAuthenticationCondition="${inargs:cancel}">
|
||||
<Entry method="authenticate" state="cossa_realm_TokenExchangeEndpoint"/>
|
||||
<Entry method="authenticate" state="cossa_realm_TokenExchangeEndpoint" selector="${request:currentResource:^http[s]?\u003A//[^/]+/.*$:true}"/>
|
||||
<Entry method="stepup" state="cossa_realm_Selector"/>
|
||||
<Entry method="stepup" state="cossa_realm_TokenExchangeEndpoint" selector="${request:currentResource:^http[s]?\u003A//[^/]+/.*$:true}"/>
|
||||
</Domain>
|
||||
<AuthState name="cossa_realm_TokenExchangeEndpoint" class="ch.adnovum.cossa.TokenExchangeEndpoint" final="false" resumeState="true">
|
||||
<!-- source: pattern://89578db79d2bc15d55e11141 -->
|
||||
<ResultCond name="failed" next="cossa_realm_New_Authentication_Failed"/>
|
||||
<!-- source: pattern://89578db79d2bc15d55e11141 -->
|
||||
<ResultCond name="ok" next="cossa_realm_IdTokenVerification"/>
|
||||
<!-- source: pattern://89578db79d2bc15d55e11141 -->
|
||||
<Response value="AUTH_CONTINUE">
|
||||
<!-- source: pattern://89578db79d2bc15d55e11141 -->
|
||||
<Gui name="Default"/>
|
||||
</Response>
|
||||
<!-- source: pattern://89578db79d2bc15d55e11141 -->
|
||||
<property name="clientId" value="client1"/>
|
||||
<!-- source: pattern://89578db79d2bc15d55e11141 -->
|
||||
<property name="clientSecret" value="clientPassword"/>
|
||||
</AuthState>
|
||||
<AuthState name="cossa_realm_New_Authentication_Failed" class="ch.nevis.esauth.auth.states.standard.AuthError" final="false">
|
||||
<!-- source: pattern://72e29eb80a951e518ce123e4 -->
|
||||
<Response value="AUTH_ERROR">
|
||||
<!-- source: pattern://72e29eb80a951e518ce123e4 -->
|
||||
<Gui name="Error">
|
||||
<!-- source: pattern://72e29eb80a951e518ce123e4 -->
|
||||
<GuiElem name="info" type="error" label="error_99"/>
|
||||
<!-- source: pattern://72e29eb80a951e518ce123e4 -->
|
||||
<GuiElem name="submit" type="button" label="continue.button.label"/>
|
||||
</Gui>
|
||||
</Response>
|
||||
</AuthState>
|
||||
<AuthState name="cossa_realm_IdTokenVerification" class="ch.adnovum.cossa.IdTokenVerification" final="false" resumeState="true">
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<ResultCond name="failed" next="cossa_realm_Authentication_Failed"/>
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<ResultCond name="ok" next="cossa_realm_Prepare_Done"/>
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<Response value="AUTH_CONTINUE">
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<Gui name="Default"/>
|
||||
</Response>
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<property name="Issuer" value="https://login.sandbox.pre.swissid.ch:443/idp/oauth2"/>
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<property name="clientId" value="klp-client"/>
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<property name="jwkSetURL" value="https://login.sandbox.pre.swissid.ch/idp/oauth2/connect/jwk_uri"/>
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<property name="httpclient.tls.trustAll" value="true"/>
|
||||
</AuthState>
|
||||
<AuthState name="cossa_realm_Authentication_Failed" class="ch.nevis.esauth.auth.states.standard.AuthError" final="false">
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<Response value="AUTH_ERROR">
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<Gui name="Error">
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<GuiElem name="info" type="error" label="error_99"/>
|
||||
<!-- source: pattern://a976546c6a56dc04c0d34592 -->
|
||||
<GuiElem name="submit" type="button" label="continue.button.label"/>
|
||||
</Gui>
|
||||
</Response>
|
||||
</AuthState>
|
||||
<AuthState name="cossa_realm_Prepare_Done" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07, pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<ResultCond name="default" next="cossa_realm_Auth_Done"/>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07, pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<Response value="AUTH_DONE">
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07, pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<Gui name="ContinueResponse"/>
|
||||
</Response>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07, pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<property name="script" value="file:///var/opt/nevisauth/default/conf/prepare_done.groovy"/>
|
||||
</AuthState>
|
||||
<AuthState name="cossa_realm_Auth_Done" class="ch.nevis.esauth.auth.states.standard.AuthDone" final="false">
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07, pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<Response value="AUTH_DONE">
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07, pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<Gui name="ContinueResponse"/>
|
||||
</Response>
|
||||
</AuthState>
|
||||
<AuthState name="cossa_realm_Selector" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="false">
|
||||
<!-- source: pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<ResultCond name="New_JWT_Token" next="cossa_realm_New_JWT_Token"/>
|
||||
<!-- source: pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<ResultCond name="nomatch" next="cossa_realm_Prepare_Done"/>
|
||||
<!-- source: pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<Response value="AUTH_ERROR">
|
||||
<!-- source: pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
|
||||
</Response>
|
||||
<!-- source: pattern://b67f81a971e4c08aa79040a2 -->
|
||||
<property name="condition:New_JWT_Token" value="${request:requiredRoles:^token.New_JWT_Token$:true}"/>
|
||||
</AuthState>
|
||||
<AuthState name="cossa_realm_New_JWT_Token" class="ch.nevis.esauth.auth.states.jwt.JWTToken" final="false">
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<ResultCond name="ok" next="cossa_realm_Prepare_Done" authLevel="token.New_JWT_Token"/>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<Response value="AUTH_ERROR">
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
|
||||
</Response>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<property name="token.type" value="JWS"/>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<property name="token.algorithm" value="RS256"/>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<property name="keystoreref" value="DefaultKeyStore"/>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<property name="keyobjectref" value="Signer_New_JWT_Token"/>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<property name="token.identifier" value="token.New_JWT_Token"/>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<property name="out.issuer" value="test"/>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<property name="out.subject" value="test"/>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<property name="out.time_to_live" value="28800"/>
|
||||
<!-- source: pattern://0e9ac4dab4d3a5992a7f1b07 -->
|
||||
<property name="out.custom.exp" value="test"/>
|
||||
</AuthState>
|
||||
</AuthEngine>
|
||||
</esauth-server>
|
|
@ -0,0 +1,31 @@
|
|||
Configuration:
|
||||
monitorInterval: 60
|
||||
Appenders:
|
||||
Console:
|
||||
- name: "SERVER"
|
||||
target: "SYSTEM_OUT"
|
||||
PatternLayout:
|
||||
pattern: "[esauth4sv.log] %d{ISO8601} %-15.15t %mdc{trace_id} %mdc{span_id} %-20.20c %-5.5p %m%n"
|
||||
RegexFilter:
|
||||
regex: ".*GET /nevisauth/liveness.*"
|
||||
onMatch: "DENY"
|
||||
onMismatch: "ACCEPT"
|
||||
Loggers:
|
||||
Logger:
|
||||
- name: "EsAuthStart"
|
||||
level: "INFO"
|
||||
- name: "org.apache.catalina.loader.WebappClassLoader"
|
||||
level: "FATAL"
|
||||
- name: "org.apache.catalina.startup.HostConfig"
|
||||
level: "ERROR"
|
||||
- name: "ch.nevis.esauth.events"
|
||||
level: "FATAL"
|
||||
- name: "AuthEngine"
|
||||
level: "INFO"
|
||||
- name: "Vars"
|
||||
level: "INFO"
|
||||
Root:
|
||||
level: "WARN"
|
||||
additivity: "false"
|
||||
AppenderRef:
|
||||
- ref: "SERVER"
|
|
@ -0,0 +1,16 @@
|
|||
server:
|
||||
name: "default"
|
||||
protocol: "https"
|
||||
port: "8991"
|
||||
host: "0.0.0.0"
|
||||
tls:
|
||||
keystore: "/var/opt/keys/own/nai-default-identity/keystore.p12"
|
||||
keystore-passphrase: "${exec:/var/opt/keys/own/nai-default-identity/keypass}"
|
||||
client-auth: "required"
|
||||
truststore: "/var/opt/keys/trust/nai-default-tls-client-trust/truststore.p12"
|
||||
truststore-passphrase: "${exec:/var/opt/keys/trust/nai-default-tls-client-trust/keypass}"
|
||||
management:
|
||||
server:
|
||||
port: "9000"
|
||||
healthchecks:
|
||||
enabled: "true"
|
|
@ -0,0 +1,4 @@
|
|||
otel.service.name = nai
|
||||
otel.traces.exporter = none
|
||||
otel.metrics.exporter = none
|
||||
otel.logs.exporter = none
|
|
@ -0,0 +1,23 @@
|
|||
// nevisProxy replaces the entire AUTH: scope when new outargs are returned by nevisAuth.
|
||||
// Thus, we have to store tokens in the session (as a String) and restore them on subsequent step-ups.
|
||||
|
||||
// restore tokens
|
||||
session.each { key, value ->
|
||||
if (key.startsWith('outarg.token.')) {
|
||||
def name = key.substring(7)
|
||||
if (outargs.containsKey(name)) {
|
||||
LOG.debug("not restoring token (outarg: $name) from session: outarg already set")
|
||||
}
|
||||
else {
|
||||
LOG.debug("restoring token (outarg: $name) from session")
|
||||
outargs.put(name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// store tokens
|
||||
outargs.each { name, value ->
|
||||
if (name.startsWith('token.')) {
|
||||
session.put('outarg.' + name, value)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# NAME
|
||||
# status.sh - Checks the status of the nevisAuth instance.
|
||||
#
|
||||
# SYNOPSIS
|
||||
# status.sh
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Performs periodic checks until the instance is up or broken or timeout is reached.
|
||||
# The script terminates when the process of the instance stops running.
|
||||
# There are no arguments for this script.
|
||||
#
|
||||
# EXIT CODES
|
||||
# 0 Instance is up.
|
||||
# 1 Instance process is not running.
|
||||
# 2 Instance is broken.
|
||||
# 3 Timeout reached.
|
||||
|
||||
# Defines how much we should sleep between checking if the instance is up.
|
||||
interval=1
|
||||
# Defines how much we should wait the instance to start up until we give up and exit.
|
||||
timeout=70
|
||||
((end_time=${SECONDS}+$timeout))
|
||||
|
||||
# Checks if the process of the instance is still running.
|
||||
# Arguments:
|
||||
# None
|
||||
# Returns:
|
||||
# In case it is running, returns 0, otherwise non-zero (exit code of systemctl).
|
||||
isProcessRunning() {
|
||||
systemctl is-active --quiet nevisauth@default
|
||||
IS_RUNNING=$?
|
||||
return $IS_RUNNING
|
||||
}
|
||||
|
||||
# Checks if the instance is up. (Attempts connecting to the instance)
|
||||
# Arguments:
|
||||
# None
|
||||
# Returns:
|
||||
# If the connection was successful and the instance up (is not broken), returns 0.
|
||||
# If the connection was not successful, returns 1.
|
||||
checkInstance() {
|
||||
lsof -i :8991 -sTCP:LISTEN
|
||||
EXIT_CODE=$?
|
||||
return $EXIT_CODE
|
||||
}
|
||||
|
||||
# This function encapsulates the logic of checking if the process is running and if the instance is up.
|
||||
# In case the process is not running, exits with exit code 1.
|
||||
# Arguments:
|
||||
# None
|
||||
# Returns:
|
||||
# If the instance process is running, returns the result of the instance check function.
|
||||
check() {
|
||||
if isProcessRunning
|
||||
then
|
||||
checkInstance
|
||||
CS=$?
|
||||
return $CS
|
||||
else
|
||||
echo "Process is not running."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check the status of the instance periodically.
|
||||
while ((${SECONDS} < ${end_time}))
|
||||
do
|
||||
sleep ${interval}
|
||||
if check
|
||||
then
|
||||
echo "Instance is up."
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Exceeded check timeout (70s). Instance is down."
|
||||
exit 3
|
|
@ -0,0 +1,53 @@
|
|||
apiVersion: "operator.nevis-security.ch/v1"
|
||||
kind: "NevisComponent"
|
||||
metadata:
|
||||
name: "nli"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
||||
labels:
|
||||
deploymentTarget: "nli"
|
||||
annotations:
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "7dc20659babc66c4401ce0dd"
|
||||
spec:
|
||||
type: "NevisLogrend"
|
||||
replicas: 1
|
||||
version: "8.2405.0"
|
||||
gitInitVersion: "1.3.0"
|
||||
runAsNonRoot: true
|
||||
ports:
|
||||
server: 8988
|
||||
management: 8997
|
||||
resources:
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "1000Mi"
|
||||
requests:
|
||||
cpu: "10m"
|
||||
memory: "500Mi"
|
||||
livenessProbe:
|
||||
management:
|
||||
httpGet:
|
||||
path: "/nevislogrend/liveness"
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 6
|
||||
readinessProbe:
|
||||
server:
|
||||
tcpSocket: true
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 4
|
||||
startupProbe:
|
||||
server:
|
||||
tcpSocket: true
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 4
|
||||
failureThreshold: 50
|
||||
podDisruptionBudget:
|
||||
maxUnavailable: "50%"
|
||||
git:
|
||||
tag: "r-21011ba803abc5d21a228c7db0c6d53bb2575702"
|
||||
dir: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT/DEFAULT-ADN-POST-IAM-TKNXCHNG-INV/nli"
|
||||
credentials: "git-credentials"
|
||||
podSecurity:
|
||||
policy: "baseline"
|
||||
automountServiceAccountToken: false
|
||||
timeZone: "Europe/Zurich"
|
|
@ -0,0 +1,18 @@
|
|||
schemaVersion: 1.0
|
||||
instance:
|
||||
type: "nevislogrend"
|
||||
name: "default"
|
||||
directory: "/var/opt/nevislogrend/default"
|
||||
pid: "systemctl show nevislogrend@default -p MainPID | cut -d '=' -f2"
|
||||
source:
|
||||
url: "/nevisadmin/#/projects/DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT/patterns/7dc20659babc66c4401ce0dd"
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "7dc20659babc66c4401ce0dd"
|
||||
patternClass: "ch.nevis.admin.v4.plugin.nevisauth.patterns.NevisLogrendDeployable"
|
||||
resources:
|
||||
ports:
|
||||
- "0.0.0.0:8988"
|
||||
control:
|
||||
start: "systemctl restart nevislogrend@default"
|
||||
stop: "systemctl stop nevislogrend@default"
|
||||
status: "systemctl status nevislogrend@default"
|
|
@ -0,0 +1,14 @@
|
|||
RTENV_SECURITY_CHECK=no_shell
|
||||
|
||||
# only standalone deployment is supported with nevisAdmin 4
|
||||
LOGREND_DEPLOY_TYPE=standalone
|
||||
|
||||
JAVA_OPTS=(
|
||||
"-XX:+UseContainerSupport"
|
||||
"-Dfile.encoding=UTF-8"
|
||||
"-XX:MaxRAMPercentage=80.0"
|
||||
"-javaagent:/opt/agent/opentelemetry-javaagent.jar"
|
||||
"-Dotel.javaagent.logging=application"
|
||||
"-Dotel.javaagent.configuration-file=/var/opt/nevislogrend/default/conf/otel.properties"
|
||||
"-Dotel.resource.attributes=service.version=8.2405.0,service.instance.id=$HOSTNAME"
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
Configuration:
|
||||
monitorInterval: 60
|
||||
Appenders:
|
||||
Console:
|
||||
- name: "SERVER"
|
||||
target: "SYSTEM_OUT"
|
||||
PatternLayout:
|
||||
pattern: "[nevislogrend.log] %d{ISO8601} %-15.15t %mdc{trace_id} %mdc{span_id} %-40.40c %-5.5p %m%n"
|
||||
RegexFilter:
|
||||
regex: ".*GET /nevislogrend/health.*"
|
||||
onMatch: "DENY"
|
||||
onMismatch: "ACCEPT"
|
||||
Loggers:
|
||||
Logger: []
|
||||
Root:
|
||||
level: "WARN"
|
||||
additivity: "false"
|
||||
AppenderRef:
|
||||
- ref: "SERVER"
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
application.accept.loginapplicationidfromuri=no
|
||||
application.gui.litdict=yes
|
||||
application.gui.substitution=yes
|
||||
application.input.charset=UTF-8
|
||||
application.inputs.htmlencode=yes
|
||||
application.loginapp.current=
|
||||
application.loginapp.default=cossa_realm
|
||||
application.loginapp.override=header:channel
|
||||
application.package.name=nevislogrend
|
||||
application.render.content.type=text/html; charset=UTF-8
|
||||
application.url.obfuscate=no
|
||||
application.webdata.path={0}{2}{1}
|
||||
application.webdata.pathparam=logrendresourcepath
|
||||
application.webdata.pathparam.default=/login/resources
|
||||
cache.revalidate.delay=15
|
||||
cache.source=file
|
||||
keytag.end=}
|
||||
keytag.start=${
|
||||
management.healthchecks.enabled=true
|
||||
path.config=/var/opt/nevislogrend/default/conf
|
||||
path.instance=/var/opt/nevislogrend/default
|
||||
server.host=0.0.0.0
|
||||
server.name=default
|
||||
server.port=8988
|
||||
server.protocol=http
|
|
@ -0,0 +1,3 @@
|
|||
ico=image/x-icon
|
||||
woff=font/woff
|
||||
woff2=font/woff2
|
|
@ -0,0 +1,4 @@
|
|||
otel.service.name = nli
|
||||
otel.traces.exporter = none
|
||||
otel.metrics.exporter = none
|
||||
otel.logs.exporter = none
|
|
@ -0,0 +1,26 @@
|
|||
# source: pattern://b67f81a971e4c08aa79040a2
|
||||
application.countries.default=CH
|
||||
# source: pattern://b67f81a971e4c08aa79040a2
|
||||
cache.file.exempt=
|
||||
# source: pattern://b67f81a971e4c08aa79040a2
|
||||
cache.filefolder.exempt=
|
||||
# source: pattern://b67f81a971e4c08aa79040a2
|
||||
application.language.source.1=param:language
|
||||
# source: pattern://b67f81a971e4c08aa79040a2
|
||||
application.language.source.2=cookie:LANG
|
||||
# source: pattern://b67f81a971e4c08aa79040a2
|
||||
application.language.source.3=gui
|
||||
# source: pattern://b67f81a971e4c08aa79040a2
|
||||
application.language.source.4=browser
|
||||
# source: pattern://b67f81a971e4c08aa79040a2
|
||||
application.languages=en,de,fr,it
|
||||
# source: pattern://b67f81a971e4c08aa79040a2
|
||||
application.languages.default=en
|
||||
# source: pattern://7dc20659babc66c4401ce0dd
|
||||
application.language.cookie.en=LANG:en
|
||||
# source: pattern://7dc20659babc66c4401ce0dd
|
||||
application.language.cookie.de=LANG:de
|
||||
# source: pattern://7dc20659babc66c4401ce0dd
|
||||
application.language.cookie.fr=LANG:fr
|
||||
# source: pattern://7dc20659babc66c4401ce0dd
|
||||
application.language.cookie.it=LANG:it
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
language.de=Deutsch
|
||||
language.en=English
|
||||
language.fr=Français
|
||||
language.it=Italiano
|
||||
title=NEVIS SSO Portal
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
language.de=Deutsch
|
||||
language.en=English
|
||||
language.fr=Français
|
||||
language.it=Italiano
|
||||
title=NEVIS SSO Portal
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
language.de=Deutsch
|
||||
language.en=English
|
||||
language.fr=Français
|
||||
language.it=Italiano
|
||||
title=NEVIS SSO Portal
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
language.de=Deutsch
|
||||
language.en=English
|
||||
language.fr=Français
|
||||
language.it=Italiano
|
||||
title=NEVIS SSO Portal
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
language.de=Deutsch
|
||||
language.en=English
|
||||
language.fr=Français
|
||||
language.it=Italiano
|
||||
title=NEVIS SSO Portal
|
|
@ -0,0 +1,165 @@
|
|||
let baseURL; // base URL
|
||||
let statusToken; // used to check progress
|
||||
let dispatcherElement; // to display link or QR code
|
||||
let infoElement; // to display info text
|
||||
let errorElement; // to display error text
|
||||
|
||||
function addInput(form, name, value) {
|
||||
const input = document.createElement("input");
|
||||
input.name = name;
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
}
|
||||
|
||||
function submitStatus(status) {
|
||||
// we have to do a form POST instead of AJAX
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.style.display = "none";
|
||||
addInput(form, "status", status);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
const Status = {
|
||||
_pollInterval: 2 * 1000, // Check every 2 seconds
|
||||
latest: null,
|
||||
|
||||
startPolling: function (token, uiCallback) {
|
||||
let interval = setInterval(async () => {
|
||||
await this._check(token).then(function (resp) {
|
||||
console.log("Polling status: %o", resp);
|
||||
uiCallback && uiCallback(resp, false);
|
||||
return Status.latest = resp;
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error("Error during polling: %o", err);
|
||||
return false;
|
||||
});
|
||||
if (Status.latest && (Status.latest.status === 'succeeded' || Status.latest.status === 'failed' || Status.latest.status === 'unknown')) {
|
||||
// Done!
|
||||
console.log('Latest status is: %o', this.latest);
|
||||
uiCallback && uiCallback(this.latest, true);
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, this._pollInterval);
|
||||
},
|
||||
|
||||
_check: async function (token) {
|
||||
const payload = { statusToken: token };
|
||||
const response = await fetch(baseURL + 'api/v1/status', {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
cache: 'no-cache',
|
||||
credentials: 'omit',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
redirect: 'follow',
|
||||
referrerPolicy: 'no-referrer'
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
};
|
||||
|
||||
function setDeepLinkLabel(button) {
|
||||
const text = document.getElementsByName('info.deeplink')[0].value;
|
||||
button.innerHTML = text;
|
||||
}
|
||||
|
||||
function messageScanQR() {
|
||||
const text = document.getElementsByName('info.qrcode')[0].value;
|
||||
infoElement.innerHTML = text;
|
||||
}
|
||||
|
||||
function messageCheckPhone() {
|
||||
const text = document.getElementsByName('info.check.phone')[0].value;
|
||||
infoElement.innerHTML = text;
|
||||
}
|
||||
|
||||
const Element = {
|
||||
|
||||
_elem: null, // QR code or deep link depending on device
|
||||
|
||||
show: function (appLink) {
|
||||
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
||||
const isIphone = 'iPhone' === navigator.platform;
|
||||
const isAndroid = /android/i.test(userAgent) && /mobile/i.test(userAgent);
|
||||
if (isAndroid || isIphone) {
|
||||
this._elem = document.createElement('a');
|
||||
this._elem.setAttribute('href', appLink);
|
||||
this._elem.setAttribute('class', 'btn btn-primary');
|
||||
this._elem.setAttribute('target', '_blank');
|
||||
dispatcherElement.appendChild(this._elem);
|
||||
setDeepLinkLabel(this._elem);
|
||||
}
|
||||
else {
|
||||
const authenticationType = document.getElementsByName('authenticationType')[0].value;
|
||||
if (authenticationType == 'push') {
|
||||
messageCheckPhone();
|
||||
}
|
||||
else {
|
||||
messageScanQR();
|
||||
this._elem = document.createElement('canvas');
|
||||
dispatcherElement.appendChild(this._elem);
|
||||
var qrcode = new QRious({
|
||||
element: this._elem,
|
||||
foreground: "#168CA9",
|
||||
level: "M",
|
||||
size: 280,
|
||||
value: appLink
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
// hide the element which was shown
|
||||
if (this._elem != null) {
|
||||
this._elem.style.display = "none";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function authenticateUser(appLink) {
|
||||
Element.show(appLink);
|
||||
console.log('Starting Authentication Cloud status polling...');
|
||||
Status.startPolling(statusToken, (st, done) => {
|
||||
if (st.status === 'succeeded') {
|
||||
console.log('Authentication Cloud login done.');
|
||||
submitStatus('succeeded')
|
||||
}
|
||||
else if (st.status === 'failed') {
|
||||
// failed: The transaction failed, either by timeout or because the user did not accept.
|
||||
console.warn('Authentication Cloud login failed. User abort or timeout.');
|
||||
submitStatus('failed')
|
||||
}
|
||||
else if (st.status === 'unknown') {
|
||||
console.error('Authentication Cloud login failed. Unknown status.');
|
||||
submitStatus('unknown')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
||||
const form = document.getElementById('authcloud_login');
|
||||
|
||||
baseURL = form.url.value;
|
||||
statusToken = form.statusToken.value;
|
||||
|
||||
infoElement = document.getElementById('authcloud_info');
|
||||
errorElement = document.getElementById('authcloud_error');
|
||||
|
||||
dispatcherElement = document.getElementById('authcloud_dispatch');
|
||||
|
||||
const appLink = form.appLink.value;
|
||||
authenticateUser(appLink);
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
init();
|
||||
};
|
|
@ -0,0 +1,154 @@
|
|||
let baseURL; // base URL
|
||||
let statusToken; // used to check progress
|
||||
let dispatcherElement; // to display link or QR code
|
||||
let infoElement; // to display info text
|
||||
let errorElement; // to display error text
|
||||
|
||||
function addInput(form, name, value) {
|
||||
const input = document.createElement("input");
|
||||
input.name = name;
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
}
|
||||
|
||||
function submitStatus(status) {
|
||||
// we have to do a form POST instead of AJAX
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.style.display = "none";
|
||||
addInput(form, "status", status);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
const Status = {
|
||||
_pollInterval: 2 * 1000, // Check every 2 seconds
|
||||
latest: null,
|
||||
|
||||
startPolling: function (token, uiCallback) {
|
||||
let interval = setInterval(async () => {
|
||||
await this._check(token).then(function (resp) {
|
||||
console.log("Polling status: %o", resp);
|
||||
uiCallback && uiCallback(resp, false);
|
||||
return Status.latest = resp;
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error("Error during polling: %o", err);
|
||||
return false;
|
||||
});
|
||||
if (Status.latest && (Status.latest.status === 'succeeded' || Status.latest.status === 'failed' || Status.latest.status === 'unknown')) {
|
||||
// Done!
|
||||
console.log('Latest status is: %o', this.latest);
|
||||
uiCallback && uiCallback(this.latest, true);
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, this._pollInterval);
|
||||
},
|
||||
|
||||
_check: async function (token) {
|
||||
const payload = { statusToken: token };
|
||||
const response = await fetch(baseURL + 'api/v1/status', {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
cache: 'no-cache',
|
||||
credentials: 'omit',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
redirect: 'follow',
|
||||
referrerPolicy: 'no-referrer'
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
};
|
||||
|
||||
function setDeepLinkLabel(button) {
|
||||
const text = document.getElementsByName('info.deeplink')[0].value;
|
||||
button.innerHTML = text;
|
||||
}
|
||||
|
||||
function messageScanQR() {
|
||||
const text = document.getElementsByName('info.qrcode')[0].value;
|
||||
infoElement.innerHTML = text;
|
||||
}
|
||||
|
||||
const Element = {
|
||||
|
||||
_elem: null, // QR code or deep link depending on device
|
||||
|
||||
show: function (appLink) {
|
||||
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
||||
const isIphone = 'iPhone' === navigator.platform;
|
||||
const isAndroid = /android/i.test(userAgent) && /mobile/i.test(userAgent);
|
||||
if (isAndroid || isIphone) {
|
||||
this._elem = document.createElement('a');
|
||||
this._elem.setAttribute('href', appLink);
|
||||
this._elem.setAttribute('class', 'btn btn-primary');
|
||||
this._elem.setAttribute('target', '_blank');
|
||||
dispatcherElement.appendChild(this._elem);
|
||||
setDeepLinkLabel(this._elem);
|
||||
}
|
||||
else {
|
||||
messageScanQR();
|
||||
this._elem = document.createElement('canvas');
|
||||
dispatcherElement.appendChild(this._elem);
|
||||
var qrcode = new QRious({
|
||||
element: this._elem,
|
||||
foreground: "#168CA9",
|
||||
level: "M",
|
||||
size: 280,
|
||||
value: appLink
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
// hide the element which was shown
|
||||
if (this._elem != null) {
|
||||
this._elem.style.display = "none";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function onboardUser(appLink) {
|
||||
Element.show(appLink);
|
||||
console.log('Starting Authentication Cloud status polling...');
|
||||
Status.startPolling(statusToken, (st, done) => {
|
||||
if (st.status === 'succeeded') {
|
||||
console.log('Authentication Cloud onboarding done.');
|
||||
submitStatus('succeeded')
|
||||
}
|
||||
else if (st.status === 'failed') {
|
||||
// failed: The transaction failed, either by timeout or because the user did not accept.
|
||||
console.warn('Authentication Cloud onboarding failed. User abort or timeout.');
|
||||
submitStatus('failed')
|
||||
}
|
||||
else if (st.status === 'unknown') {
|
||||
console.error('Authentication Cloud onboarding failed. Unknown status.');
|
||||
submitStatus('unknown')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
||||
const form = document.getElementById('authcloud_onboard');
|
||||
|
||||
baseURL = form.url.value;
|
||||
statusToken = form.statusToken.value;
|
||||
|
||||
infoElement = document.getElementById('authcloud_info');
|
||||
errorElement = document.getElementById('authcloud_error');
|
||||
|
||||
dispatcherElement = document.getElementById('authcloud_dispatch');
|
||||
|
||||
const appLink = form.appLink.value;
|
||||
onboardUser(appLink);
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
init();
|
||||
};
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Base64URL-ArrayBuffer
|
||||
* https://github.com/herrjemand/Base64URL-ArrayBuffer
|
||||
*
|
||||
* Copyright (c) 2017 Yuriy Ackermann <ackermann.yuriy@gmail.com>
|
||||
* Copyright (c) 2012 Niklas von Hertzen
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
*/
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
||||
// Use a lookup table to find the index.
|
||||
var lookup = new Uint8Array(256);
|
||||
for (var i = 0; i < chars.length; i++) {
|
||||
lookup[chars.charCodeAt(i)] = i;
|
||||
}
|
||||
|
||||
var encode = function(arraybuffer) {
|
||||
var bytes = new Uint8Array(arraybuffer),
|
||||
i, len = bytes.length, base64 = "";
|
||||
|
||||
for (i = 0; i < len; i+=3) {
|
||||
base64 += chars[bytes[i] >> 2];
|
||||
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
|
||||
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
|
||||
base64 += chars[bytes[i + 2] & 63];
|
||||
}
|
||||
|
||||
if ((len % 3) === 2) {
|
||||
base64 = base64.substring(0, base64.length - 1);
|
||||
} else if (len % 3 === 1) {
|
||||
base64 = base64.substring(0, base64.length - 2);
|
||||
}
|
||||
|
||||
return base64;
|
||||
};
|
||||
|
||||
var decode = function(base64) {
|
||||
var bufferLength = base64.length * 0.75,
|
||||
len = base64.length, i, p = 0,
|
||||
encoded1, encoded2, encoded3, encoded4;
|
||||
|
||||
var arraybuffer = new ArrayBuffer(bufferLength),
|
||||
bytes = new Uint8Array(arraybuffer);
|
||||
|
||||
for (i = 0; i < len; i+=4) {
|
||||
encoded1 = lookup[base64.charCodeAt(i)];
|
||||
encoded2 = lookup[base64.charCodeAt(i+1)];
|
||||
encoded3 = lookup[base64.charCodeAt(i+2)];
|
||||
encoded4 = lookup[base64.charCodeAt(i+3)];
|
||||
|
||||
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
|
||||
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
|
||||
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
|
||||
}
|
||||
|
||||
return arraybuffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exporting and stuff
|
||||
*/
|
||||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
||||
module.exports = {
|
||||
'encode': encode,
|
||||
'decode': decode
|
||||
}
|
||||
|
||||
} else {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([], function() {
|
||||
return {
|
||||
'encode': encode,
|
||||
'decode': decode
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.base64url = {
|
||||
'encode': encode,
|
||||
'decode': decode
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,222 @@
|
|||
/********************************************************
|
||||
* Layout
|
||||
********************************************************/
|
||||
|
||||
html { /* magic to position footer */
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-bottom: 76px; /* == footer height */
|
||||
}
|
||||
|
||||
.container, .container-fluid {
|
||||
padding-left: 36px;
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
nav {
|
||||
min-height: 100px;
|
||||
padding: 36px;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 16px; /* h1.logintitle adds 20px => 36px */
|
||||
}
|
||||
|
||||
.container {
|
||||
min-width: 260px;
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 0 36px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* Header
|
||||
********************************************************/
|
||||
|
||||
header .logo {
|
||||
/* width: 20%;*/
|
||||
/*max-width: 600px;*/
|
||||
max-height: 150px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* Dropdown
|
||||
********************************************************/
|
||||
a.dropdown-toggle {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.dropdown-toggle:hover {
|
||||
color: #168CA9;
|
||||
border-bottom: 3px solid #168CA9;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.dropdown-menu li > a {
|
||||
padding: 6px 28px;
|
||||
}
|
||||
|
||||
.dropdown-menu a > .prefix {
|
||||
display: inline-block;
|
||||
min-width: 22px;
|
||||
margin-right: 28px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* Form
|
||||
********************************************************/
|
||||
|
||||
/* Labels should not be bold */
|
||||
label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Make error messages bold */
|
||||
.has-error .help-block {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Change button size, by default 116px in width */
|
||||
.btn {
|
||||
min-width: 116px;
|
||||
padding: 3px 12px;
|
||||
}
|
||||
|
||||
/* Disable gradient in buttons, ughhhh */
|
||||
.btn.btn-primary {
|
||||
border-color: transparent;
|
||||
background-image: none;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
}
|
||||
|
||||
.help-block a, .help-block a:visited {
|
||||
color: #168CA9;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.help-block a:hover {
|
||||
color: #168CA9;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* Footer
|
||||
********************************************************/
|
||||
footer .row {
|
||||
margin: 36px 0 0 0;
|
||||
height: 40px;
|
||||
padding-top: 14px;
|
||||
line-height: 26px; /* to center text: height - padding-top = 26px */
|
||||
border-top: 1px solid #168CA9;
|
||||
}
|
||||
|
||||
footer .row > div { /* Fix alignment between border + text on Bootstrap grid */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
footer .logo-round-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
footer .logo-round {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: -33px; /* found visually with Chrome Dev Tools */
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
border: 1px solid #00868c;
|
||||
border-radius: 18px;
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
footer .logo-round > img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#dispatchTargets {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* Social login
|
||||
********************************************************/
|
||||
.btn.line {
|
||||
background-color: transparent;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 1.5em 0 1em;
|
||||
border: 0.5px solid #ccc;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn.socialLogin {
|
||||
background-color: #fff;
|
||||
border: thin solid #ccc;
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
margin: 5px;
|
||||
min-width: 140px;
|
||||
width: 210px;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.socialLogin img {
|
||||
width: 1.5em;
|
||||
height: 108%;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.btn.apple img {
|
||||
width: 1.2em;
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* Show password
|
||||
********************************************************/
|
||||
.icon-inside {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon-inside input {
|
||||
padding-right: calc(0.75rem + 1.25rem + 0.75rem);
|
||||
}
|
||||
|
||||
.icon-inside button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
margin-top: 0.45rem;
|
||||
margin-right: 0.45rem;
|
||||
background: #FFFFFF;
|
||||
border: #FFFFFF;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
(function() {
|
||||
var closeDropdownTimeout;
|
||||
|
||||
function closeDropdown(event) {
|
||||
var dropdowns = document.querySelectorAll('.dropdown');
|
||||
for (var i = 0; i < dropdowns.length; i++) {
|
||||
var dropdownMenu = dropdowns[i].querySelector('.dropdown-menu');
|
||||
if (dropdownMenu.style.display !== 'none' && !dropdowns[i].contains(event.target)) {
|
||||
dropdownMenu.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// remove event listener till we have a new dropdown menu open
|
||||
if (document.querySelector('.dropdown-menu:not([style*="display: none"])') === null) {
|
||||
document.removeEventListener('click', closeDropdown);
|
||||
}
|
||||
}
|
||||
|
||||
var dropdowns = document.querySelectorAll('.dropdown');
|
||||
for (var i = 0; i < dropdowns.length; i++) {
|
||||
var dropdownMenu = dropdowns[i].querySelector('.dropdown-menu');
|
||||
dropdownMenu.style.display = 'none'; // ensure menu is initially hidden
|
||||
|
||||
dropdowns[i].addEventListener('click', function(e) {
|
||||
// show dropdown menu
|
||||
var dropdownMenu = this.querySelector('.dropdown-menu');
|
||||
dropdownMenu.style.display = 'block';
|
||||
|
||||
// handle clicking away
|
||||
clearTimeout(closeDropdownTimeout);
|
||||
closeDropdownTimeout = setTimeout(function() {
|
||||
document.addEventListener('click', closeDropdown);
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
}());
|
|
@ -0,0 +1,98 @@
|
|||
var e2eenc = function() {
|
||||
|
||||
this.encryptForm = function(algoString, formId) {
|
||||
// TODO: in case of an error we should return false, to prevent the for to be submitted
|
||||
// or replace the fields with dummy values, just to prevent the the transmission
|
||||
// of unencrypted values
|
||||
|
||||
|
||||
// create the array of input fields to encrypt (needs to be done before setting the form
|
||||
// invisible
|
||||
var fieldsToEncrypt = new Array();
|
||||
$.each($("form input:visible"), function(index, _inputField) { fieldsToEncrypt.push($(_inputField));});
|
||||
|
||||
// hide the form, and display the splash screen
|
||||
$('#loginform').css('display','none');
|
||||
$('#e2eeSplashScreen').css('display','block');
|
||||
|
||||
// encryption logic
|
||||
var pubKey = $("input[name='e2eenc.publicKey']").val();
|
||||
|
||||
var kemSessionKey = readPublicKeyAndGenerateSessionKey(pubKey)
|
||||
var iv = forge.random.getBytesSync(16);
|
||||
keyB64 = forge.util.encode64(kemSessionKey.key);
|
||||
encapsulationB64 = forge.util.encode64(kemSessionKey.encapsulation);
|
||||
ivB64 = forge.util.encode64(iv);
|
||||
|
||||
//console.log("Encrypting form " + formId + " (" + algoString + ")");
|
||||
var fields = "";
|
||||
$.each(fieldsToEncrypt, function(index, _inputField) {
|
||||
var inputField = $(_inputField);
|
||||
if (inputField.attr("type") == "text" || inputField.attr("type") == "password") {
|
||||
//console.log("Encrypting field " + JSON.stringify(inputField));
|
||||
var plainValue = inputField.val();
|
||||
|
||||
var encryptedValueB64 = encrypt(kemSessionKey, iv, plainValue);
|
||||
//console.log("Setting encrypted value in b64: " + encryptedValueB64);
|
||||
inputField.val(encryptedValueB64);
|
||||
if (fields.length > 0) {
|
||||
fields = fields + ","
|
||||
}
|
||||
fields = fields + inputField.attr("name");
|
||||
}
|
||||
});
|
||||
$("input[name='e2eenc.iv']").val(ivB64);
|
||||
$("input[name='e2eenc.encapsulation']").val(encapsulationB64);
|
||||
$("input[name='e2eenc.fields']").val(fields);
|
||||
}
|
||||
|
||||
function getRSApublicKey(pem) {
|
||||
//console.log("PEM: " + pem);
|
||||
|
||||
var msg = forge.pem.decode(pem)[0];
|
||||
|
||||
//console.log("msg type: " + msg.type);
|
||||
|
||||
if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
||||
throw new Error('Could not retrieve RSA public key from PEM; PEM is encrypted.');
|
||||
}
|
||||
|
||||
// convert DER to ASN.1 object
|
||||
var asn1obj = forge.asn1.fromDer(msg.body);
|
||||
//console.log("ASN.1 obj: " + JSON.stringify(asn1obj))
|
||||
|
||||
var pubKey = forge.pki.publicKeyFromAsn1(asn1obj)
|
||||
//console.log("PubKey: " + JSON.stringify(pubKey))
|
||||
return pubKey;
|
||||
}
|
||||
|
||||
function generateKEMSessionKey(rsaPublicKey) {
|
||||
// generate key-derivation-function and initializes it with sha1
|
||||
var kdf1 = new forge.kem.kdf1(forge.md.sha1.create());
|
||||
// creates a KEM function based on the key-derivation-function created above
|
||||
var kem = forge.kem.rsa.create(kdf1);
|
||||
// generate and encapsulate a 16-byte secret key.
|
||||
// The secret key is generated using the kdf defined above.
|
||||
var kemSessionKey = kem.encrypt(rsaPublicKey, 16);
|
||||
// kemSessionKey has 'encapsulation' (= pub key) and 'key' (= generated secret key)
|
||||
return kemSessionKey;
|
||||
}
|
||||
|
||||
function readPublicKeyAndGenerateSessionKey(pem) {
|
||||
var rsaPublicKey = getRSApublicKey(pem);
|
||||
//console.log("PubKey: " + JSON.stringify(rsaPublicKey))
|
||||
var kemSessionKey = generateKEMSessionKey(rsaPublicKey);
|
||||
//console.log("KEM session key: " + JSON.stringify(kemSessionKey))
|
||||
return kemSessionKey;
|
||||
}
|
||||
|
||||
function encrypt(kemSessionKey, iv, msg) {
|
||||
var cipher = forge.cipher.createCipher('AES-CBC', kemSessionKey.key);
|
||||
cipher.start({iv: iv});
|
||||
cipher.update(forge.util.createBuffer(msg, 'utf-8'));
|
||||
cipher.finish();
|
||||
var encrypted = cipher.output.getBytes();
|
||||
encryptedB64 = forge.util.encode64(encrypted);
|
||||
return encryptedB64;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="22" height="20" viewBox="0 0 22 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 1L5.58916 4.58916M20 19L16.4112 15.4112M12.8749 16.8246C12.2677 16.9398 11.6411 17 11.0005 17C6.52281 17 2.73251 14.0571 1.45825 9.99997C1.80515 8.8955 2.33851 7.87361 3.02143 6.97118M8.87868 7.87868C9.42157 7.33579 10.1716 7 11 7C12.6569 7 14 8.34315 14 10C14 10.8284 13.6642 11.5784 13.1213 12.1213M8.87868 7.87868L13.1213 12.1213M8.87868 7.87868L5.58916 4.58916M13.1213 12.1213L5.58916 4.58916M13.1213 12.1213L16.4112 15.4112M5.58916 4.58916C7.14898 3.58354 9.00656 3 11.0004 3C15.4781 3 19.2684 5.94291 20.5426 10C19.8357 12.2507 18.3545 14.1585 16.4112 15.4112" stroke="#6D7C80" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 769 B |
|
@ -0,0 +1,4 @@
|
|||
<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 8C14 9.65685 12.6569 11 11 11C9.34315 11 8 9.65685 8 8C8 6.34315 9.34315 5 11 5C12.6569 5 14 6.34315 14 8Z" stroke="#6D7C80" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M1.45825 7.99997C2.73253 3.94288 6.52281 1 11.0004 1C15.4781 1 19.2684 3.94291 20.5426 8.00004C19.2684 12.0571 15.4781 15 11.0005 15C6.52281 15 2.73251 12.0571 1.45825 7.99997Z" stroke="#6D7C80" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 585 B |
|
@ -0,0 +1,61 @@
|
|||
(function() {
|
||||
'use strict'
|
||||
|
||||
async function assertion(options) {
|
||||
let credential;
|
||||
try {
|
||||
credential = await navigator.credentials.get({ "publicKey": options });
|
||||
}
|
||||
// Cancel and timeout can occur besides error
|
||||
catch (error) {
|
||||
console.error(`Failed to get WebAuthn credential: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
// as this is the last call we have to do a top-level request instead of AJAX
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.style.display = "none";
|
||||
addInput(form, "path", "/nevisfido/fido2/assertion/result")
|
||||
addInput(form, "id", credential.id);
|
||||
addInput(form, "type", credential.type);
|
||||
addInput(form, "response.clientDataJSON", base64url.encode(credential.response.clientDataJSON));
|
||||
addInput(form, "response.authenticatorData", base64url.encode(credential.response.authenticatorData));
|
||||
addInput(form, "response.signature", base64url.encode(credential.response.signature));
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function authenticate() {
|
||||
// WebAuthn feature detection
|
||||
if (!isWebAuthnSupportedByTheBrowser()) {
|
||||
cancelFido2();
|
||||
return;
|
||||
};
|
||||
|
||||
const request = {};
|
||||
request.path = "/nevisfido/fido2/attestation/options";
|
||||
|
||||
// calling nevisFIDO through nevisAuth on current URL using AJAX
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(options => {
|
||||
options.challenge = base64url.decode(options.challenge);
|
||||
options.allowCredentials = options.allowCredentials.map((c) => {
|
||||
c.id = base64url.decode(c.id);
|
||||
return c;
|
||||
});
|
||||
return assertion(options);
|
||||
}).catch((error) => {
|
||||
console.error(`Error during FIDO2 authentication: ${error}`);
|
||||
cancelFido2();
|
||||
});
|
||||
}
|
||||
|
||||
authenticate();
|
||||
})();
|
|
@ -0,0 +1,175 @@
|
|||
(function() {
|
||||
'use strict'
|
||||
|
||||
async function authenticate(username, params) {
|
||||
|
||||
try {
|
||||
const { authenticationOptionsEndpoint, authenticationEndpoint, statusServiceEndpoint, userVerification, originalResource, nevisAuthEndpoint } = params;
|
||||
const { startAuthentication } = SimpleWebAuthnBrowser;
|
||||
|
||||
// fetch authentication options from nevisFIDO and save the returned fido2SessionId for later use
|
||||
const authOptRespJson = await getAuthenticationOptions(username, userVerification, nevisAuthEndpoint);
|
||||
const fido2SessionId = authOptRespJson.fido2SessionId;
|
||||
|
||||
// do the client side authentication using the SimpleWebAuthn JS library
|
||||
const authRespJson = await startAuthentication(authOptRespJson);
|
||||
|
||||
// in case the authentication response does not contain a userHandle (e.g. virtual authenticators used in system tests)
|
||||
// then we have to obtain it (in our case it is the IDM extId) using the Status Service since at the moment nevisFIDO always expects it
|
||||
if (!authRespJson.response.userHandle) {
|
||||
const statusRespJson = await getFido2SessionStatus(fido2SessionId, statusServiceEndpoint);
|
||||
|
||||
if (statusRespJson && statusRespJson.userId) {
|
||||
console.log("adding userHandle: " + statusRespJson.userId);
|
||||
authRespJson.response.userHandle = btoa(statusRespJson.userId); // add missing userHandle
|
||||
}
|
||||
else {
|
||||
throw new Error('userHandle is missing and could not determine it using the status service');
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("userHandle already set: " + authRespJson.response.userHandle);
|
||||
}
|
||||
|
||||
// send the assertion response created by the authenticator to nevisFIDO
|
||||
const serverRespJson = await submitAssertion(authRespJson, authenticationEndpoint);
|
||||
|
||||
// checking the server response of nevisFIDO
|
||||
if ((!serverRespJson) || (serverRespJson && serverRespJson.status !== 'ok')) {
|
||||
let errorMessage = (serverRespJson && serverRespJson.errorMessage) ? serverRespJson.errorMessage : 'unexpected error';
|
||||
throw new Error('authentication failed: ' + errorMessage);
|
||||
}
|
||||
|
||||
// send a request to nevisAuth with the fido2SessionId in the header to trigger the synchronisation of the
|
||||
// nevisFIDO and nevisAuth sessions (FIDO2 AuthState -> SyncFido2SessionStatusHandler) to reach AUTH_DONE
|
||||
await updateNevisAuth(fido2SessionId, nevisAuthEndpoint);
|
||||
|
||||
console.log('authentication was successful');
|
||||
|
||||
console.log('reloading page...');
|
||||
window.location.reload();
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error during FIDO2 authentication: ${error}`);
|
||||
cancelFido2();
|
||||
}
|
||||
};
|
||||
|
||||
async function getAuthenticationOptions(username, userVerification, authenticationOptionsEndpoint) {
|
||||
|
||||
const authOptReqJson = {
|
||||
'username': username,
|
||||
'userVerification': userVerification,
|
||||
};
|
||||
|
||||
const authOptReq = JSON.stringify(authOptReqJson);
|
||||
console.log('authOptReq ==> ' + authOptReq);
|
||||
|
||||
const authOptResp = await fetch(authenticationOptionsEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: authOptReq,
|
||||
});
|
||||
|
||||
if (!authOptResp.ok) {
|
||||
throw new Error('authOptResp error: HTTP ' + authOptResp.status + ' ' + authOptResp.statusText);
|
||||
}
|
||||
|
||||
const authOptRespJson = await authOptResp.json()
|
||||
console.log('authOptResp <== ' + JSON.stringify(authOptRespJson));
|
||||
|
||||
return authOptRespJson;
|
||||
};
|
||||
|
||||
async function getFido2SessionStatus(fido2SessionId, statusServiceEndpoint) {
|
||||
|
||||
const statusReqJson = {
|
||||
'fido2SessionId': fido2SessionId,
|
||||
};
|
||||
|
||||
const statusReq = JSON.stringify(statusReqJson);
|
||||
console.log('statusReq ==> ' + statusReq);
|
||||
|
||||
const statusResp = await fetch(statusServiceEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: statusReq,
|
||||
});
|
||||
|
||||
if (!statusResp.ok) {
|
||||
throw new Error('statusResp error: HTTP ' + statusResp.status + ' ' + statusResp.statusText);
|
||||
}
|
||||
|
||||
const statusRespJson = await statusResp.json();
|
||||
console.log('statusResp <== ' + JSON.stringify(statusRespJson));
|
||||
|
||||
return statusRespJson;
|
||||
}
|
||||
|
||||
async function submitAssertion(authRespJson, authenticationEndpoint) {
|
||||
|
||||
console.log("submitting assertion for userHandle: " + authRespJson.response.userHandle);
|
||||
|
||||
// TODO koenig 20230504: read btoa once nevisFIDO is adapted
|
||||
let encodedAuthResp = {
|
||||
"id": authRespJson.id,
|
||||
"response": {
|
||||
"authenticatorData": authRespJson.response.authenticatorData,
|
||||
"signature": authRespJson.response.signature,
|
||||
"userHandle": authRespJson.response.userHandle,
|
||||
"clientDataJSON": authRespJson.response.clientDataJSON
|
||||
},
|
||||
"type": authRespJson.type
|
||||
}
|
||||
|
||||
const authResp = JSON.stringify(encodedAuthResp);
|
||||
console.log('authResp ==> ' + authResp);
|
||||
|
||||
const serverResp = await fetch(authenticationEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: authResp,
|
||||
});
|
||||
|
||||
if (!serverResp.ok) {
|
||||
throw new Error('submitAssertion error: HTTP ' + submitAssertion.status + ' ' + submitAssertion.statusText);
|
||||
}
|
||||
|
||||
const serverRespJson = await serverResp.json();
|
||||
console.log('serverResp <== ' + JSON.stringify(serverRespJson));
|
||||
|
||||
return serverRespJson;
|
||||
};
|
||||
|
||||
async function updateNevisAuth(fido2SessionId, nevisAuthEndpoint) {
|
||||
|
||||
console.log('updateNevisAuth ==> ' + fido2SessionId);
|
||||
|
||||
const updateNevisAuthResponse = await fetch(nevisAuthEndpoint, {
|
||||
method: 'GET',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'nevis-fido2-session-id': fido2SessionId,
|
||||
}
|
||||
});
|
||||
|
||||
if (!updateNevisAuthResponse.ok) {
|
||||
throw new Error('updateNevisAuthResponse error: HTTP ' + updateNevisAuthResponse.status + ' ' + updateNevisAuthResponse.statusText);
|
||||
}
|
||||
|
||||
console.log('updateNevisAuth <== OK');
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
// TODO koenig 20230206: we don't generate IDs into the HTML yet
|
||||
let username = document.getElementsByName("username")[0].value;
|
||||
params.nevisAuthEndpoint = window.location.href;
|
||||
authenticate(username, params);
|
||||
})();
|
|
@ -0,0 +1,70 @@
|
|||
function dispatch(name) {
|
||||
// we have to do a top-level request instead of AJAX
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.style.display = "none";
|
||||
addInput(form, name, "true");
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
async function attestation(options) {
|
||||
let credential;
|
||||
try {
|
||||
credential = await navigator.credentials.create({ "publicKey": options });
|
||||
}
|
||||
// cancel and timeout can occur besides error
|
||||
catch (error) {
|
||||
console.error(`Failed to create WebAuthn credential: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
// as this is the last call we have to do a top-level request instead of AJAX
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.style.display = "none";
|
||||
addInput(form, "path", "/nevisfido/fido2/attestation/result")
|
||||
addInput(form, "id", credential.id);
|
||||
addInput(form, "type", credential.type);
|
||||
addInput(form, "response.clientDataJSON", base64url.encode(credential.response.clientDataJSON));
|
||||
addInput(form, "response.attestationObject", base64url.encode(credential.response.attestationObject));
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function start() {
|
||||
|
||||
if (!isWebAuthnSupportedByTheBrowser()) {
|
||||
dispatch("unsupported");
|
||||
return;
|
||||
};
|
||||
|
||||
const request = {};
|
||||
request.path = "/nevisfido/fido2/attestation/options";
|
||||
|
||||
// calling nevisFIDO through nevisAuth on current URL using AJAX
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(options => {
|
||||
options.user.id = base64url.decode(options.user.id);
|
||||
options.challenge = base64url.decode(options.challenge);
|
||||
if (options.excludeCredentials != null) {
|
||||
options.excludeCredentials = options.excludeCredentials.map((c) => {
|
||||
c.id = base64url.decode(c.id);
|
||||
return c;
|
||||
});
|
||||
}
|
||||
if (options.authenticatorSelection.authenticatorAttachment === null) {
|
||||
options.authenticatorSelection.authenticatorAttachment = undefined;
|
||||
}
|
||||
return attestation(options);
|
||||
}).catch((error) => {
|
||||
console.log('Error during FIDO2 onboarding: ' + error);
|
||||
dispatch("failed");
|
||||
});
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
function addInput(form, name, value) {
|
||||
const input = document.createElement("input");
|
||||
input.name = name;
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether WebAuthn is supported by the browser or not.
|
||||
* @return true if supported, false if it is not supported or not in secure context
|
||||
*/
|
||||
function isWebAuthnSupportedByTheBrowser() {
|
||||
if (window.isSecureContext) {
|
||||
// This feature is available only in secure contexts in some or all supporting browsers.
|
||||
if ('credentials' in navigator) {
|
||||
return true;
|
||||
}
|
||||
console.warn('Oh no! This browser does not support WebAuthn.');
|
||||
return false;
|
||||
}
|
||||
console.warn('WebAuthn feature is available only in secure contexts. For testing over HTTP, you can use the origin "localhost".');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger on cancel pattern of the FIDO2 authentication step.
|
||||
*
|
||||
* Provides an alternative when the user decides to
|
||||
* cancel the fido2 credential operation(create or fetch) or
|
||||
* the operation fails and the error cannot be handled.
|
||||
*/
|
||||
function cancelFido2() {
|
||||
// we have to do a top-level request instead of AJAX
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.style.display = "none";
|
||||
addInput(form, "cancel_fido2", "true");
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<svg width="842" height="1e3" xmlns="http://www.w3.org/2000/svg"><path d="M702 960c-54.2 52.6-114 44.4-171 19.6-60.6-25.3-116-26.9-180 0-79.7 34.4-122 24.4-170-19.6-271-279-231-704 77-720 74.7 4 127 41.3 171 44.4 65.4-13.3 128-51.4 198-46.4 84.1 6.8 147 40 189 99.7-173 104-132 332 26.9 396-31.8 83.5-72.6 166-141 227zM423 237C414.9 113 515.4 11 631 1c15.9 143-130 250-208 236z"/></svg>
|
After Width: | Height: | Size: 386 B |
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(1, 0, 0, 1, 27.009001, -39.238998)">
|
||||
<path fill="#4285F4" d="M -3.264 51.509 C -3.264 50.719 -3.334 49.969 -3.454 49.239 L -14.754 49.239 L -14.754 53.749 L -8.284 53.749 C -8.574 55.229 -9.424 56.479 -10.684 57.329 L -10.684 60.329 L -6.824 60.329 C -4.564 58.239 -3.264 55.159 -3.264 51.509 Z"/>
|
||||
<path fill="#34A853" d="M -14.754 63.239 C -11.514 63.239 -8.804 62.159 -6.824 60.329 L -10.684 57.329 C -11.764 58.049 -13.134 58.489 -14.754 58.489 C -17.884 58.489 -20.534 56.379 -21.484 53.529 L -25.464 53.529 L -25.464 56.619 C -23.494 60.539 -19.444 63.239 -14.754 63.239 Z"/>
|
||||
<path fill="#FBBC05" d="M -21.484 53.529 C -21.734 52.809 -21.864 52.039 -21.864 51.239 C -21.864 50.439 -21.724 49.669 -21.484 48.949 L -21.484 45.859 L -25.464 45.859 C -26.284 47.479 -26.754 49.299 -26.754 51.239 C -26.754 53.179 -26.284 54.999 -25.464 56.619 L -21.484 53.529 Z"/>
|
||||
<path fill="#EA4335" d="M -14.754 43.989 C -12.984 43.989 -11.404 44.599 -10.154 45.789 L -6.734 42.369 C -8.804 40.429 -11.514 39.239 -14.754 39.239 C -19.444 39.239 -23.494 41.939 -25.464 45.859 L -21.484 48.949 C -20.534 46.099 -17.884 43.989 -14.754 43.989 Z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" aria-label="Microsoft" role="img" viewBox="0 0 512 512"><rect width="512" height="512" rx="15%" fill="#fff"/><path d="M75 75v171h171v-171z" fill="#f25022"/><path d="M266 75v171h171v-171z" fill="#7fba00"/><path d="M75 266v171h171v-171z" fill="#00a4ef"/><path d="M266 266v171h171v-171z" fill="#ffb900"/></svg>
|
After Width: | Height: | Size: 347 B |
|
@ -0,0 +1,31 @@
|
|||
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient x1="8.042%" y1="0%" x2="65.682%" y2="23.865%" id="a">
|
||||
<stop stop-color="#168CA9" stop-opacity="0" offset="0%"/>
|
||||
<stop stop-color="#168CA9" stop-opacity=".631" offset="63.146%"/>
|
||||
<stop stop-color="#168CA9" offset="100%"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(1 1)">
|
||||
<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke="url(#a)" stroke-width="2">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 18 18"
|
||||
to="360 18 18"
|
||||
dur="0.9s"
|
||||
repeatCount="indefinite" />
|
||||
</path>
|
||||
<circle fill="#fff" cx="36" cy="18" r="1">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 18 18"
|
||||
to="360 18 18"
|
||||
dur="0.9s"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 68 KiB |
|
@ -0,0 +1,119 @@
|
|||
(function () {
|
||||
|
||||
function createForm() {
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.style.display = "none";
|
||||
return form;
|
||||
}
|
||||
|
||||
function addInput(form, name, value) {
|
||||
const input = document.createElement("input");
|
||||
input.name = name;
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
}
|
||||
|
||||
let statusPolling;
|
||||
|
||||
function dispatchLink() {
|
||||
|
||||
document.getElementById("mauth_started").style.display = "block"; // show
|
||||
|
||||
const request = {};
|
||||
|
||||
// calling nevisFIDO through nevisAuth on current URL using AJAX
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
}).then(res => {
|
||||
res.json().then(o => {
|
||||
// example response: {"dispatchResult":"..."}
|
||||
if (o.dispatchResult == 'dispatched') {
|
||||
// example response: {..., "dispatcherInformation":{..., "response":"admin4testing://authenticate?dispatchTokenResponse=ey..."}}
|
||||
var link = o.dispatcherInformation.response;
|
||||
console.log("received link: " + link);
|
||||
var linkElem = document.getElementById("mauth_link");
|
||||
linkElem.href = link; // custom scheme link does not work in Android 13
|
||||
const isMobile = !!/(iPhone|iPad|Android)/.test(window.navigator.userAgent);
|
||||
if (isMobile) {
|
||||
document.getElementById("mauth_link_parent").style.display = "inline"; // show
|
||||
}
|
||||
var url = new URL(link);
|
||||
var dispatchTokenResponse = url.searchParams.get("dispatchTokenResponse");
|
||||
// render QR code
|
||||
var qrCodeElem = document.getElementById("mauth_qrcode");
|
||||
var qrcode = new QRious({
|
||||
element: qrCodeElem,
|
||||
foreground: "#168CA9",
|
||||
level: "M",
|
||||
size: 256,
|
||||
value: link
|
||||
});
|
||||
var sessionId = o.sessionId;
|
||||
console.log("started polling for session ID: " + sessionId);
|
||||
statusPolling = window.setInterval(function () {
|
||||
poll(sessionId);
|
||||
}, 2000);
|
||||
}
|
||||
else {
|
||||
console.log("authentication failed: " + o.dispatchResult);
|
||||
const form = createForm();
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}).catch((err) => console.error("error: ", err));
|
||||
}
|
||||
|
||||
function poll(sessionId) {
|
||||
|
||||
const request = {};
|
||||
request.fidoUafSessionId = sessionId;
|
||||
|
||||
// calling nevisFIDO through nevisAuth on current URL using AJAX
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
}).then(res => {
|
||||
res.json().then(o => {
|
||||
var status = o.status;
|
||||
console.log("status: " + status);
|
||||
if (status == 'clientAuthenticating') {
|
||||
// show process icon
|
||||
document.getElementById("mauth_loading").style.display = 'block';
|
||||
// hide QR-code and information
|
||||
document.getElementById("mauth_qrcode").style.display = 'none';
|
||||
document.getElementById("mauth_qrcode_info").style.display = 'none';
|
||||
}
|
||||
if (status == 'succeeded') {
|
||||
clearInterval(statusPolling);
|
||||
// as this is the last call we have to do a top-level request instead of AJAX
|
||||
const form = createForm();
|
||||
addInput(form, "continue", "true"); // required for custom dispatching in usernameless
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
else if (status == 'failed' || status == 'unknown') {
|
||||
|
||||
clearInterval(statusPolling);
|
||||
console.error("authentication failed with status: " + status);
|
||||
|
||||
// as this is the last call we have to do a top-level request instead of AJAX
|
||||
const form = createForm();
|
||||
addInput(form, "fidoUafSessionId", sessionId);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}).catch((err) => console.error("error: ", err));
|
||||
}
|
||||
|
||||
dispatchLink();
|
||||
})();
|
|
@ -0,0 +1,106 @@
|
|||
(function () {
|
||||
|
||||
function createForm() {
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.style.display = "none";
|
||||
return form;
|
||||
}
|
||||
|
||||
function addInput(form, name, value) {
|
||||
const input = document.createElement("input");
|
||||
input.name = name;
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
}
|
||||
|
||||
let statusPolling;
|
||||
|
||||
function renderEnrollment() {
|
||||
|
||||
// link is provided by a hidden GuiElem
|
||||
var link = document.getElementsByName("mauth_dispatcher_link")[0].value;
|
||||
console.log("received dispatcher link: " + link);
|
||||
|
||||
const isMobile = !!/(iPhone|iPad|Android)/.test(window.navigator.userAgent);
|
||||
if (isMobile) {
|
||||
var linkElem = document.getElementById("mauth_link");
|
||||
linkElem.href = link;
|
||||
document.getElementById("mauth_link_parent").style.display = "inline"; // show
|
||||
}
|
||||
|
||||
var url = new URL(link);
|
||||
var dispatchTokenResponse = url.searchParams.get("dispatchTokenResponse");
|
||||
|
||||
// render QR code into mauth_qrcode element
|
||||
var qrCodeElem = document.getElementById("mauth_qrcode");
|
||||
var qrcode = new QRious({
|
||||
element: qrCodeElem,
|
||||
foreground: "#168CA9",
|
||||
level: "M",
|
||||
size: 256,
|
||||
value: link
|
||||
});
|
||||
|
||||
// show entire element
|
||||
document.getElementById("mauth_started").style.display = "block";
|
||||
|
||||
console.log("scheduling status polling (2s interval)");
|
||||
statusPolling = window.setInterval(function () {
|
||||
poll();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function poll() {
|
||||
|
||||
// state is held on backend side
|
||||
const request = {};
|
||||
|
||||
// calling nevisFIDO through nevisAuth on current URL using AJAX
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
}).then(res => {
|
||||
res.json().then(o => {
|
||||
|
||||
var status = o.status;
|
||||
console.log("status: " + status);
|
||||
|
||||
if (status == 'clientRegistering') {
|
||||
|
||||
// show process icon
|
||||
document.getElementById("mauth_loading").style.display = 'block';
|
||||
|
||||
// hide QR-code and information
|
||||
document.getElementById("mauth_qrcode").style.display = 'none';
|
||||
document.getElementById("mauth_qrcode_info").style.display = 'none';
|
||||
}
|
||||
else if (status == 'succeeded') {
|
||||
|
||||
clearInterval(statusPolling);
|
||||
console.error("onboarding successful");
|
||||
|
||||
// as this is the last call we have to do a top-level request instead of AJAX
|
||||
const form = createForm();
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
else if (status == 'failed' || status == 'unknown') {
|
||||
|
||||
clearInterval(statusPolling);
|
||||
console.error("onboarding failed with status: " + status);
|
||||
|
||||
// as this is the last call we have to do a top-level request instead of AJAX
|
||||
const form = createForm();
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}).catch((err) => console.error("error: ", err));
|
||||
}
|
||||
|
||||
renderEnrollment();
|
||||
})();
|
|
@ -0,0 +1,172 @@
|
|||
(function () {
|
||||
|
||||
function createForm() {
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.style.display = "none";
|
||||
return form;
|
||||
}
|
||||
|
||||
function addInput(form, name, value) {
|
||||
const input = document.createElement("input");
|
||||
input.name = name;
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
}
|
||||
|
||||
let statusPolling;
|
||||
|
||||
function dispatch(id) {
|
||||
|
||||
document.getElementById("mauth_devices").style.display = "none"; // hide selection menu
|
||||
document.getElementById("mauth_started").style.display = "block"; // show
|
||||
|
||||
const request = {};
|
||||
request.dispatchTargetId = id;
|
||||
request.dispatcher = "firebase-cloud-messaging";
|
||||
|
||||
// calling nevisFIDO through nevisAuth on current URL using AJAX
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
}).then(res => {
|
||||
res.json().then(o => {
|
||||
console.log("dispatch response: " + JSON.stringify(o));
|
||||
// example response: {"dispatchResult":"..."}
|
||||
if (o.dispatchResult == 'dispatched') {
|
||||
// example response: {"token":"...","sessionId":"...","dispatchResult":"dispatched","dispatcherInformation":{"name":"firebase-cloud-messaging","response":"..."}}
|
||||
console.log("push dispatching successful");
|
||||
// set numbers for number matching
|
||||
if (o.channelLinking) {
|
||||
document.getElementById('mauth_match_numbers').innerHTML = o.channelLinking.content;
|
||||
}
|
||||
// preparing content for QR-code
|
||||
var token = o.token;
|
||||
console.log("found token: " + token);
|
||||
// hidden GuiElem
|
||||
var redeemUrl = document.querySelector('input[name=redeem_url]').value;
|
||||
console.log("found redeem URL: " + redeemUrl);
|
||||
let qrCodeContents = {
|
||||
nma_data_version: "1",
|
||||
nma_data_content_type: "application/json",
|
||||
nma_data: {
|
||||
token: token,
|
||||
redeem_url: redeemUrl
|
||||
}
|
||||
};
|
||||
var qrCodeValue = window.btoa(JSON.stringify(qrCodeContents));
|
||||
// render QR code
|
||||
var qrCodeElem = document.getElementById("mauth_qrcode");
|
||||
console.log("rendering QR code");
|
||||
var qrcode = new QRious({
|
||||
element: qrCodeElem,
|
||||
foreground: "#168CA9",
|
||||
level: "M",
|
||||
size: 256,
|
||||
value: qrCodeValue
|
||||
});
|
||||
var sessionId = o.sessionId;
|
||||
console.log("started polling for session ID: " + sessionId);
|
||||
statusPolling = window.setInterval(function () {
|
||||
poll(sessionId);
|
||||
}, 2000);
|
||||
}
|
||||
else {
|
||||
console.log("authentication failed: " + o.dispatchResult);
|
||||
const form = createForm();
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}).catch((err) => console.error("error: ", err));
|
||||
}
|
||||
|
||||
function renderDeviceList() {
|
||||
|
||||
const request = {};
|
||||
|
||||
// calling nevisFIDO through nevisAuth on current URL using AJAX
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
}).then(res => {
|
||||
res.json().then(o => {
|
||||
// example response: {"dispatchTargets":[{"id":"40a41ac7-0189-4c0b-8db9-cafcaa3e3f11","name":"Android Google Pixel 4 23.11.2022 07:26:25"}]}
|
||||
var devices = o.dispatchTargets;
|
||||
if (devices.length > 1) {
|
||||
console.log("multiple devices found, selection menu required.");
|
||||
let list = document.getElementById("mauth_devices");
|
||||
for (let i = 0; i < devices.length; i++) {
|
||||
let device = devices[i];
|
||||
var item = document.createElement("li");
|
||||
item.class = "list-group-item list-group-item-action";
|
||||
item.onclick = function() { dispatch(device.id) };
|
||||
item.innerHTML += device.name;
|
||||
list.appendChild(item);
|
||||
}
|
||||
list.style.display = "block"; // show selection menu
|
||||
}
|
||||
else if (devices.length == 1) {
|
||||
console.log("user has only 1 device, no selection required.");
|
||||
dispatch(devices[0].id);
|
||||
}
|
||||
else {
|
||||
console.error("user has no device.");
|
||||
// TODO koenig 20221124: design this case
|
||||
}
|
||||
});
|
||||
}).catch((err) => console.error("error: ", err));
|
||||
}
|
||||
|
||||
function poll(sessionId) {
|
||||
|
||||
const request = {};
|
||||
request.fidoUafSessionId = sessionId;
|
||||
|
||||
// calling nevisFIDO through nevisAuth on current URL using AJAX
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
}).then(res => {
|
||||
res.json().then(o => {
|
||||
var status = o.status;
|
||||
console.log("status: " + status);
|
||||
if (status == 'clientAuthenticating') {
|
||||
document.getElementById("mauth_qrcode").style.display = 'none';
|
||||
document.getElementById("mauth_qrcode_info").style.display = 'none';
|
||||
document.getElementById("mauth_match_numbers").style.display = 'block';
|
||||
document.getElementById("mauth_loading").style.display = 'block';
|
||||
}
|
||||
if (status == 'succeeded') {
|
||||
clearInterval(statusPolling);
|
||||
// as this is the last call we have to do a top-level request instead of AJAX
|
||||
const form = createForm();
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
else if (status == 'failed' || status == 'unknown') {
|
||||
|
||||
clearInterval(statusPolling);
|
||||
console.error("authentication failed with status: " + status);
|
||||
|
||||
// as this is the last call we have to do a top-level request instead of AJAX
|
||||
const form = createForm();
|
||||
addInput(form, "fidoUafSessionId", sessionId);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}).catch((err) => console.error("error: ", err));
|
||||
}
|
||||
|
||||
renderDeviceList();
|
||||
})();
|
|
@ -0,0 +1,119 @@
|
|||
(function () {
|
||||
|
||||
function createForm() {
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.style.display = "none";
|
||||
return form;
|
||||
}
|
||||
|
||||
function addInput(form, name, value) {
|
||||
const input = document.createElement("input");
|
||||
input.name = name;
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
}
|
||||
|
||||
let statusPolling;
|
||||
|
||||
function dispatch() {
|
||||
|
||||
console.log("initiating usernameless mobile authentication...");
|
||||
|
||||
document.getElementById("mauth_started").style.display = "block"; // show
|
||||
|
||||
const request = {};
|
||||
|
||||
// calling nevisFIDO through nevisAuth on current URL using AJAX
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
}).then(res => {
|
||||
res.json().then(o => {
|
||||
console.log(o);
|
||||
// example response: {"dispatchResult":"..."}
|
||||
if (o.dispatchResult == 'dispatched') {
|
||||
// example response: {..., "dispatcherInformation":{..., "response":"admin4testing://authenticate?dispatchTokenResponse=ey..."}}
|
||||
var link = o.dispatcherInformation.response;
|
||||
console.log("received link: " + link);
|
||||
var linkElem = document.getElementById("mauth_link");
|
||||
linkElem.href = link; // custom scheme link does not work in Android 13
|
||||
const isMobile = !!/(iPhone|iPad|Android)/.test(window.navigator.userAgent);
|
||||
if (isMobile) {
|
||||
document.getElementById("mauth_link_parent").style.display = "inline"; // show
|
||||
}
|
||||
var url = new URL(link);
|
||||
var dispatchTokenResponse = url.searchParams.get("dispatchTokenResponse");
|
||||
// render QR code
|
||||
var qrCodeElem = document.getElementById("mauth_qrcode");
|
||||
var qrcode = new QRious({
|
||||
element: qrCodeElem,
|
||||
foreground: "#168CA9",
|
||||
level: "M",
|
||||
size: 256,
|
||||
value: link
|
||||
});
|
||||
var sessionId = o.sessionId;
|
||||
console.log("started polling for session ID: " + sessionId);
|
||||
statusPolling = window.setInterval(function () {
|
||||
poll(sessionId);
|
||||
}, 2000);
|
||||
}
|
||||
else {
|
||||
console.log("authentication failed: " + o.dispatchResult);
|
||||
const form = createForm();
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}).catch((err) => console.error("error: ", err));
|
||||
}
|
||||
|
||||
function poll(sessionId) {
|
||||
|
||||
const request = {};
|
||||
request.fidoUafSessionId = sessionId;
|
||||
|
||||
// calling nevisFIDO through nevisAuth on current URL using AJAX
|
||||
fetch("", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
}).then(res => {
|
||||
res.json().then(o => {
|
||||
var status = o.status;
|
||||
console.log("status: " + status);
|
||||
if (status == 'clientAuthenticating') {
|
||||
document.getElementById("mauth_qrcode").style.display = 'none';
|
||||
document.getElementById("mauth_loading").style.display = 'block';
|
||||
}
|
||||
if (status == 'succeeded') {
|
||||
clearInterval(statusPolling);
|
||||
// as this is the last call we have to do a top-level request instead of AJAX
|
||||
const form = createForm();
|
||||
addInput(form, "fidoUafDone", "true"); // checked by Groovy script
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
else if (status == 'failed' || status == 'unknown') {
|
||||
|
||||
clearInterval(statusPolling);
|
||||
console.error("authentication failed with status: " + status);
|
||||
|
||||
// as this is the last call we have to do a top-level request instead of AJAX
|
||||
const form = createForm();
|
||||
addInput(form, "fidoUafSessionId", sessionId); // checked by Groovy script
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}).catch((err) => console.error("error: ", err));
|
||||
}
|
||||
|
||||
dispatch();
|
||||
})();
|
|
@ -0,0 +1,43 @@
|
|||
// display oauth scopes listed in input field 'consentInformation'
|
||||
// change 'consentInformation' and 'scope_name' to the values used in your configuration.
|
||||
$(function() {
|
||||
|
||||
var consentInformationFieldName = "consentInformation"; // name of the input field from which to parse the value as the consent information JSON
|
||||
var scopeDescriptionSource = "scope_name"; // key of the field in the consent information JSON of which to get the value as the scope description
|
||||
|
||||
function displayOAuthScopesConsent() {
|
||||
var jsonData = parseJson();
|
||||
if (jsonData !== undefined) {
|
||||
mapJsonToHtml(jsonData)
|
||||
}
|
||||
}
|
||||
|
||||
function mapJsonToHtml(jsonData) {
|
||||
mapJsonToHtmlScopeList("listOfRequestedScopesWithExistingConsent", jsonData.requestedScopesWithExistingConsent, "Already accepted scopes:");
|
||||
mapJsonToHtmlScopeList("listOfRequestedScopes", jsonData.requestedScopesRequiringConsent, "Requested scopes that require a consent:");
|
||||
}
|
||||
|
||||
function mapJsonToHtmlScopeList(elementId, scopeInformation, title) {
|
||||
if (scopeInformation !== undefined && Object.keys(scopeInformation).length > 0) {
|
||||
$("input[name=" + consentInformationFieldName +"]").after("<p style='margin-top: 0.5em'>" + title + "</p><div class='scopeinfobox'><ul id='" + elementId + "' /> </div>");
|
||||
jQuery.each(scopeInformation, function(key,value) {
|
||||
var scopeDescription = value[scopeDescriptionSource];
|
||||
if (scopeDescription) {
|
||||
$("#" + elementId).append('<li>' + scopeDescription + '</li>');
|
||||
} else {
|
||||
$("#" + elementId).append('<li>' + key + '</li>');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function parseJson() {
|
||||
var consentInformationField = $("input[name=" +consentInformationFieldName +"]");
|
||||
if (consentInformationField.length > 0) {
|
||||
return JSON.parse(consentInformationField.val());
|
||||
}
|
||||
}
|
||||
|
||||
displayOAuthScopesConsent();
|
||||
});
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
function toggleInputType(passwordInputId, eyeIconId, resourcePath) {
|
||||
const passwordInput = document.getElementById(passwordInputId);
|
||||
const eyeIcon = document.getElementById(eyeIconId);
|
||||
if (passwordInput.type === 'text') {
|
||||
passwordInput.type = 'password';
|
||||
eyeIcon.src = resourcePath + '/resources/eye.svg';
|
||||
return;
|
||||
}
|
||||
passwordInput.type = 'text';
|
||||
eyeIcon.src = resourcePath + '/resources/eye-off.svg';
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
#set($jsValidation = 1) ## enable JS validation, client-side
|
||||
|
||||
#set($useFormEncryption = $gui.encryption && ($gui.encryption.length() > 0))
|
||||
|
||||
#set($encryptionParamsOk = true)
|
||||
|
||||
#if ($useFormEncryption)
|
||||
#* check the mandatory e2eenc.publicKey GuiElem *#
|
||||
#set($encryptionParamsOk = $gui.getGuiElem("e2eenc.publicKey") && ($gui.getGuiElem("e2eenc.publicKey") != "" ))
|
||||
#end
|
||||
|
||||
#if (!$encryptionParamsOk)
|
||||
$response.setStatus(502)
|
||||
|
||||
#else
|
||||
#set($isAjaxRequest = "XMLHttpRequest" == $login.requestHeaders.get("X-Requested-With"))
|
||||
|
||||
#set($acceptHeader = $login.requestHeaders.accept)
|
||||
#if (!$acceptHeader)
|
||||
#set($acceptHeader = $login.requestHeaders.Accept)
|
||||
#end
|
||||
#if ($acceptHeader)
|
||||
#set($isHtmlRequest = $acceptHeader.contains("text/html") || $acceptHeader.contains("*/*"))
|
||||
#set($isJsonRequest = $acceptHeader.contains("application/json"))
|
||||
#set($isSoapRequest = $acceptHeader.contains("application/soap+xml"))
|
||||
#set($isXmlRequest = $acceptHeader.contains("application/xml")||$acceptHeader.contains("text/xml"))
|
||||
#set($isCssRequest = $acceptHeader.contains("text/css"))
|
||||
#else
|
||||
#set($isHtmlRequest = true)
|
||||
#set($isSoapRequest = false)
|
||||
#set($isXmlRequest = false)
|
||||
#set($isCssRequest = false)
|
||||
#end
|
||||
|
||||
## sending the query parameter render=form will render only the inner form
|
||||
#set($isFormRequest = "form" == $login.requestParameters.render && $isHtmlRequest)
|
||||
|
||||
#parse("${templatePath}/macros.vm")
|
||||
|
||||
#if ($isHtmlRequest)
|
||||
#if ($isFormRequest)
|
||||
#parse("${templatePath}/form.vm")
|
||||
#else
|
||||
## html.vm is generated from html provided via pattern
|
||||
#parse("${templatePath}/html.vm")
|
||||
#end
|
||||
#end
|
||||
|
||||
## AJAX requests: signal to JS-Client that login is required
|
||||
#if ($isAjaxRequest)
|
||||
$response.setStatus(401)
|
||||
$response.setHeader("WWW-Authenticate","$gui.domain")
|
||||
#end
|
||||
|
||||
#if (!$isHtmlRequest && $isXmlRequest)
|
||||
$response.setHeader("Content-Type","text/xml")
|
||||
## emit custom XML here, use $utils.escapeXml to sanitize values coming from clients
|
||||
#end
|
||||
|
||||
#if (!$isHtmlRequest && $isJsonRequest)
|
||||
$response.setHeader("Content-Type","application/json")
|
||||
#parse("${templatePath}/json.vm")
|
||||
## emit custom JSON here, use $utils.escapeJs to sanitize values coming from clients
|
||||
#end
|
||||
#end
|
|
@ -0,0 +1,11 @@
|
|||
<footer id="footer" class="text-primary">
|
||||
<div class="row small">
|
||||
<div class="col-md-4 hidden-xs hidden-sm">Copyright © 2023 NEVIS Security AG</div>
|
||||
<div class="col-xs-12 col-md-4 text-center text-uppercase logo-round-container">
|
||||
<div class="logo-round center-block">
|
||||
<img src="${login.appDataPath}/resources/logo.png" alt="NEVIS Security Suite">
|
||||
</div>
|
||||
<strong>NEVIS Security Suite</strong>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
|
@ -0,0 +1,127 @@
|
|||
## if only form, then we include javascript here (start of body)
|
||||
#if ($isFormRequest)
|
||||
#parse("${templatePath}/js_start.vm")
|
||||
#end
|
||||
|
||||
#set ($formTarget = $utils.escapeHtmlAttribute($gui.target.replaceAll('&?language=[^&]*','')))
|
||||
|
||||
#if ($useFormEncryption)
|
||||
<div id="e2eeSplashScreen" style="display:none;">
|
||||
<h2 class="logintitle text-center">$gui.label</h2>
|
||||
<div class="field info" id="info">$text.get("e2ee.splashscreen.msg")</div>
|
||||
</div>
|
||||
#end
|
||||
|
||||
<div id="loginform">
|
||||
|
||||
<form id="$gui.name" name="$gui.name"
|
||||
#if ($useFormEncryption) onsubmit="new e2eenc().encryptForm('$gui.encryption','$gui.name')" #end
|
||||
method="POST" target="_self" action="$formTarget" autocomplete="off" accept-charset="UTF-8" class="form-horizontal">
|
||||
|
||||
<h1 class="logintitle text-center">$gui.label</h1>
|
||||
|
||||
#set ($tabindex = 0)
|
||||
#set ($policyFailureOpen = false)
|
||||
#set ($policyInfoOpen = false)
|
||||
|
||||
#foreach ($guiElem in $gui.getGuiElems())
|
||||
#set ($tabindex = $tabindex+1)
|
||||
#if ($guiElem.name.startsWith("policyInfo") && $guiElem.label && $guiElem.label.length() > 0)
|
||||
#if (!$policyInfoOpen)
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-3 col-sm-6">
|
||||
#set ($policyInfoOpen = true)
|
||||
#end
|
||||
<span class="help-block small" id="$guiElem.name">$guiElem.label</span>
|
||||
#elseif ($guiElem.name.startsWith("policyFailure") && $guiElem.label && $guiElem.label.length() > 0)
|
||||
#if (!$policyFailureOpen)
|
||||
<div class="form-group has-error">
|
||||
<div class="col-sm-offset-3 col-sm-6">
|
||||
#set ($policyFailureOpen = true)
|
||||
#end
|
||||
<span class="help-block small" id="$guiElem.name">$guiElem.label</span>
|
||||
#else
|
||||
#if (!$guiElem.name.startsWith("policyInfo") && $policyInfoOpen) ## close
|
||||
</div>
|
||||
</div>
|
||||
#set ($policyInfoOpen = false)
|
||||
#end
|
||||
#if (!$guiElem.name.startsWith("policyFailure") && $policyFailureOpen) ## close
|
||||
</div>
|
||||
</div>
|
||||
#set ($policyFailureOpen = false)
|
||||
#end
|
||||
#renderFormField($guiElem, $gui, $tabindex)
|
||||
#end
|
||||
#end
|
||||
|
||||
## this block applies when Channel is set to Push / Link
|
||||
#if ($gui.name == "mauth_link_qr" || $gui.name == "mauth_onboard")
|
||||
<!-- shown after dispatching -->
|
||||
<center id="mauth_started">
|
||||
<img id="mauth_loading" style="width: 60px; height: 60px; display: none;" src="${login.appDataPath}/resources/loading.svg"/>
|
||||
<br><br>
|
||||
<p id="mauth_qrcode_info">$text.get("mobile_auth.scan")</p>
|
||||
<canvas id="mauth_qrcode" style="width: 256px; height: 256px;">
|
||||
</canvas>
|
||||
<div id="mauth_link_parent" class="form-group" style="display: none">
|
||||
$text.get("mobile_auth.link")
|
||||
</div>
|
||||
</center>
|
||||
#end
|
||||
|
||||
## this block applies when Channel is set to Push / QR-code (in-app)
|
||||
#if ($gui.name == "mauth_push_qr")
|
||||
<!-- shown if the user has multiple devices -->
|
||||
<ul id="mauth_devices" style="display: none">
|
||||
</ul>
|
||||
<!-- shown after selecting the device -->
|
||||
<center id="mauth_started" style="display: none">
|
||||
<img id="mauth_loading" style="width: 60px; height: 60px; display: none;" src="${login.appDataPath}/resources/loading.svg"/>
|
||||
<p id="mauth_match_numbers" style="font-size: 64px; display: none;"></p>
|
||||
<p id="mauth_qrcode_info">$text.get("mobile_auth.push-or-scan")</p>
|
||||
<canvas id="mauth_qrcode" style="width: 256px; height: 256px;">
|
||||
</canvas>
|
||||
</center>
|
||||
#end
|
||||
|
||||
## this block applies for usernameless mobile authentication
|
||||
#if ($gui.name == "mauth_usernameless")
|
||||
<center id="mauth_started" style="display: none">
|
||||
<img id="mauth_loading" style="width: 60px; height: 60px; display: none;" src="${login.appDataPath}/resources/loading.svg"/>
|
||||
<br><br>
|
||||
<p id="mauth_qrcode_info">$text.get("mobile_auth.scan")</p>
|
||||
<canvas id="mauth_qrcode" style="width: 256px; height: 256px;">
|
||||
</canvas>
|
||||
<div id="mauth_link_parent" class="form-group" style="display: none">
|
||||
<a href="" id="mauth_link">$text.get("mobile_auth.link")</a>
|
||||
</div>
|
||||
</center>
|
||||
#end
|
||||
|
||||
#if ($useFormEncryption)
|
||||
<input type="hidden" name="e2eenc.fields" value="not-set">
|
||||
<input type="hidden" name="e2eenc.iv" value="not-set">
|
||||
<input type="hidden" name="e2eenc.encapsulation" value="not-set">
|
||||
#end
|
||||
|
||||
#renderFormControls($gui)
|
||||
#renderFormLinks($gui)
|
||||
</form>
|
||||
|
||||
<!-- position input focus into first element of form -->
|
||||
<script type="text/javascript">
|
||||
const form = document.forms['$gui.name'];
|
||||
if (form) {
|
||||
const input = form.elements[0];
|
||||
if (input) {
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
## if only form, then we include javascript here (end of body)
|
||||
#if ($isFormRequest)
|
||||
#parse("${templatePath}/js_end.vm")
|
||||
#end
|
||||
</div>
|
|
@ -0,0 +1,3 @@
|
|||
<header id="header" class="container-fluid">
|
||||
<img class="logo center-block" src="${login.appDataPath}/resources/logo_animated.gif" alt="NEVIS Security Suite">
|
||||
</header>
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="${utils.escapeHtml($login.localeCode)}">
|
||||
|
||||
<head>
|
||||
<title>$text.get('title')</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<link href="${login.appDataPath}/resources/bootstrap.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="${login.appDataPath}/resources/bootstrap-theme.min.css" rel="stylesheet" type="text/css" media="all">
|
||||
<link href="${login.appDataPath}/resources/default.css" rel="stylesheet" type="text/css" media="all">
|
||||
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
|
||||
|
||||
#parse("${templatePath}/js_start.vm")
|
||||
</head>
|
||||
|
||||
<body>
|
||||
#parse("${templatePath}/lang.vm")
|
||||
|
||||
#parse("${templatePath}/header.vm")
|
||||
|
||||
<main id="content" class="container">
|
||||
#parse("${templatePath}/form.vm")
|
||||
</main>
|
||||
|
||||
#parse("${templatePath}/footer.vm")
|
||||
|
||||
#parse("${templatePath}/js_end.vm")
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,76 @@
|
|||
<script src="${login.appDataPath}/resources/dropdown.js"></script>
|
||||
<script src="${login.appDataPath}/resources/show-password.js"></script>
|
||||
|
||||
#if ($gui.name == "oauth_consent")
|
||||
<script src="${login.appDataPath}/resources/oauth_consent.js"></script>
|
||||
#end
|
||||
|
||||
#if ($gui.name == "authcloud")
|
||||
<script src="${login.appDataPath}/resources/qrious.min.js"></script>
|
||||
<script src="${login.appDataPath}/resources/authcloud.js"></script>
|
||||
#end
|
||||
|
||||
#if ($gui.name == "authcloud_onboard")
|
||||
<script src="${login.appDataPath}/resources/qrious.min.js"></script>
|
||||
<script src="${login.appDataPath}/resources/authcloud_onboard.js"></script>
|
||||
#end
|
||||
|
||||
#if ($gui.name == "authcloud_login")
|
||||
<script src="${login.appDataPath}/resources/qrious.min.js"></script>
|
||||
<script src="${login.appDataPath}/resources/authcloud_login.js"></script>
|
||||
#end
|
||||
|
||||
#if ($gui.name == "mauth_onboard")
|
||||
<script src="${login.appDataPath}/resources/qrious.min.js"></script>
|
||||
<script src="${login.appDataPath}/resources/mauth_onboard.js"></script>
|
||||
#end
|
||||
|
||||
#if ($gui.name == "mauth_link_qr")
|
||||
<script src="${login.appDataPath}/resources/qrious.min.js"></script>
|
||||
<script src="${login.appDataPath}/resources/mauth_link_qr.js"></script>
|
||||
#end
|
||||
|
||||
#if ($gui.name == "mauth_push_qr")
|
||||
<script src="${login.appDataPath}/resources/qrious.min.js"></script>
|
||||
<script src="${login.appDataPath}/resources/mauth_push_qr.js"></script>
|
||||
#end
|
||||
|
||||
#if ($gui.name == "mauth_usernameless")
|
||||
<script src="${login.appDataPath}/resources/qrious.min.js"></script>
|
||||
<script src="${login.appDataPath}/resources/mauth_usernameless.js"></script>
|
||||
#end
|
||||
|
||||
#if ($gui.name == "fido2_auth")
|
||||
<script src="${login.appDataPath}/resources/base64.js"></script>
|
||||
<script src="${login.appDataPath}/resources/fido2_utils.js"></script>
|
||||
<script src="${login.appDataPath}/resources/fido2_auth.js"></script>
|
||||
#end
|
||||
|
||||
#if ($gui.name == "fido2_auth_std")
|
||||
#set ($authenticationOptionsPath = $login.requestHeaders["fido2AuthenticationOptionsPath"])
|
||||
#set ($authenticationPath = $login.requestHeaders["fido2AuthenticationPath"])
|
||||
#set ($statusServicePath = $login.requestHeaders["fido2StatusServicePath"])
|
||||
#set ($userVerification = $login.requestHeaders["fido2UserVerification"])
|
||||
<script>
|
||||
let params = {
|
||||
authenticationOptionsEndpoint: "$authenticationOptionsPath",
|
||||
authenticationEndpoint: "$authenticationPath",
|
||||
statusServiceEndpoint: "$statusServicePath",
|
||||
userVerification: "$userVerification",
|
||||
};
|
||||
</script>
|
||||
<script src="${login.appDataPath}/resources/simplewebauthn-browser@7.1.0.min.js"></script>
|
||||
<script src="${login.appDataPath}/resources/fido2_utils.js"></script>
|
||||
<script src="${login.appDataPath}/resources/fido2_auth_std.js"></script>
|
||||
#end
|
||||
|
||||
#if ($gui.name == "fido2_onboard")
|
||||
<script src="${login.appDataPath}/resources/base64.js"></script>
|
||||
<script src="${login.appDataPath}/resources/fido2_utils.js"></script>
|
||||
<script src="${login.appDataPath}/resources/fido2_onboard.js"></script>
|
||||
#end
|
||||
|
||||
#if ($useFormEncryption)
|
||||
<script src="${login.appDataPath}/resources/forge.bundle.js"></script>
|
||||
<script src="${login.appDataPath}/resources/e2eenc.js"></script>
|
||||
#end
|
|
@ -0,0 +1 @@
|
|||
<script src="${login.appDataPath}/resources/jquery-3.6.0.min.js"></script>
|
|
@ -0,0 +1,88 @@
|
|||
## This template is used to respond with a JSON format
|
||||
## In this case, the client is supposed to parse and show the data
|
||||
## The JSON data is close to the XML format of the GuiDesc
|
||||
|
||||
#set ($target = $utils.escapeHtmlAttribute($gui.target.replaceAll('&?language=[^&]*','')))
|
||||
{
|
||||
"name" : "$gui.name" ,
|
||||
"target" : "$target" #if ($gui.label || $gui.language || $gui.domain || $gui.getGuiElems().size() > 0 || $gui.getGuiGroup().size() > 0), #end ## if
|
||||
|
||||
#if ($gui.label) "label" : "$gui.label" #if ($gui.language || $gui.domain || $gui.getGuiElems().size() > 0 || $gui.getGuiGroup().size() > 0), #end ## if
|
||||
#end ## if
|
||||
|
||||
#if ($gui.language) "language" : "$gui.language" #if ($gui.domain || $gui.getGuiElems().size() > 0 || $gui.getGuiGroup().size() > 0), #end ## if
|
||||
#end ## if ($gui.language)
|
||||
#if ($gui.domain) "domain" : "$gui.domain" #if ($gui.getGuiElems().size() > 0 || $gui.getGuiGroup().size() > 0), #end ## if
|
||||
#end ## if ($gui.domain)
|
||||
|
||||
#if ($gui.getGuiElems().size() > 0)
|
||||
"elements" : [
|
||||
#set ($i = 0)
|
||||
#foreach ($guiElem in $gui.getGuiElems())
|
||||
{
|
||||
"name" : "$guiElem.name",
|
||||
"type" : "$guiElem.type",
|
||||
"optional" : "$guiElem.optional",
|
||||
"label" : "$guiElem.label" #if ($guiElem['validation-failed'] || $guiElem.value || $guiElem.length || $guiElem.format), #end
|
||||
#if ($guiElem['validation-failed']) "validation-failed" : "$guiGroup.validationFailed" #if ($guiElem.value || $guiElem.length || $guiElem.format), #end
|
||||
#end ## if ($guiElem['validation-failed'])
|
||||
|
||||
#if ($guiElem.value) "value" : "$guiElem.value.replaceAll('\\\\','_ESCAPED_BACKSLASH_').replaceAll('\\"','_ESCAPED_QUOTE_').replaceAll('\\','\\\\').replaceAll('"','\\"').replaceAll('_ESCAPED_BACKSLASH_','\\\\').replaceAll('_ESCAPED_QUOTE_','\\"')" #if ($guiElem.length || $guiElem.format), #end
|
||||
#end ## if ($guiElem.value)
|
||||
|
||||
#if ($guiElem.length) "max-length" : "$guiElem.length" #if ($guiElem.format), #end
|
||||
#end ## if ($guiElem.length)
|
||||
|
||||
#if ($guiElem.format) "format" : "$guiElem.format"
|
||||
#end
|
||||
|
||||
}
|
||||
#set ($i = $i + 1)
|
||||
#if ($i < ($gui.getGuiElems().size())), #end
|
||||
|
||||
#end ## loop
|
||||
] #if ($gui.getGuiGroup() && $gui.getGuiGroup().size() > 0), #end
|
||||
#end ## if ($gui.getGuiGroup() && $gui.getGuiElem().size() > 0)
|
||||
#if ($gui.getGuiGroup() && $gui.getGuiGroup().size() > 0)
|
||||
"groups" : [
|
||||
#set ($j = 0)
|
||||
#foreach ($guiGroup in $gui.getGuiGroup())
|
||||
"name" : "$guiGroup.name",
|
||||
"type" : "$guiGroup.type",
|
||||
"label" : "$guiGroup.label",
|
||||
"multiple" : "$guiGroup.multiple",
|
||||
"format" : "$guiGroup.format",
|
||||
"optional" : "$guiGroup.optional",
|
||||
"validation-failed" : "$guiGroup.validationFailed" #if ($gui.getGuiElems().length() > 0), #end
|
||||
#if ($gui.getGuiElems() && $gui.getGuiElems().length() > 0)
|
||||
"elements" : [
|
||||
#set ($i = 0)
|
||||
#foreach ($guiElem in $gui.getGuiElems())
|
||||
{
|
||||
"name" : "$guiElem.name",
|
||||
"type" : "$guiElem.type",
|
||||
"optional" : "$guiElem.optional",
|
||||
"validation-failed" : "$guiGroup.validationFailed",
|
||||
"label" : "$guiElem.label" #if ($guiElem.value || $guiElem.length || $guiElem.format), #end
|
||||
#if ($guiElem.value)
|
||||
"value" : "$guiElem.value.replaceAll('\\\\','_ESCAPED_BACKSLASH_').replaceAll('\\"','_ESCAPED_QUOTE_').replaceAll('\\','\\\\').replaceAll('"','\\"').replaceAll('_ESCAPED_BACKSLASH_','\\\\').replaceAll('_ESCAPED_QUOTE_','\\"')" #if ($guiElem.length || $guiElem.format), #end
|
||||
#end ## if ($guiElem.value)
|
||||
#if ($guiElem.length)
|
||||
"max-length" : "$guiElem.length" #if ($guiElem.format), #end
|
||||
#end ## if ($guiElem.length)
|
||||
#if ($guiElem.format)
|
||||
"format" : "$guiElem.format"
|
||||
#end ## if ($guiElem.format)
|
||||
}
|
||||
#set ($i = $i + 1)
|
||||
#if ($i < ($gui.getGuiElems().size())), #end
|
||||
|
||||
#end ## loop
|
||||
] #if ($foreach.hasNext), #end
|
||||
#set ($j = $j + 1)
|
||||
#if ($j < ($gui.getGuiGroup().size())), #end
|
||||
#end ## foreach ($guiGroup in $gui.getGuiGroup())
|
||||
#end ## if ($gui.getGuiElem() && $gui.getGuiElem().size() > 0)
|
||||
]
|
||||
#end ## if ($gui.getGuiGroup() && $gui.getGuiGroup().length() > 0)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
## Nav =================================================================
|
||||
<nav id="language-switch" class="container-fluid">
|
||||
<div class="dropdown pull-right">
|
||||
<a id="language-switch-btn" class="dropdown-toggle text-uppercase small" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<strong id="language">$login.localeCode</strong>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="language-switch-btn">
|
||||
## loop over all defined languages/locales....
|
||||
#foreach ($locale in $login.locales)
|
||||
## find translated label of current locale
|
||||
#if ($text.contains("language.$locale"))
|
||||
#set ($langLabel = $text.get("language.$locale"))
|
||||
#elseif ($locale.length() > 2)
|
||||
#set ($langLabel = $text.get("language.${locale.substring(0,2).toLowercase()}"))
|
||||
#else
|
||||
#set ($langLabel = $locale)
|
||||
#end
|
||||
## emit link or text for each language
|
||||
#if ($login.localeCode != $locale && $login.language != $locale)
|
||||
#set ($langTarget = $utils.escapeHtmlAttribute($gui.target('language', $locale)))
|
||||
<li>
|
||||
<a class="lang" href="$langTarget">
|
||||
<strong class="prefix text-primary text-uppercase">$locale</strong>
|
||||
<span>$langLabel</span>
|
||||
</a>
|
||||
</li>
|
||||
#end
|
||||
#end ## end foreach
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
|
@ -0,0 +1,295 @@
|
|||
|
||||
#macro(renderFormField $guiElem, $gui, $tabindex)
|
||||
|
||||
#if ($guiElem.type == "submit" || $guiElem.type == "button" || $guiElem.type == "reset" || $guiElem.type == "link")
|
||||
## do nothing, will be rendered in renderFormControls nd renderFormLinks
|
||||
|
||||
|
||||
#elseif ($guiElem.type == "info" || $guiElem.type == "error")
|
||||
#if ($guiElem.label && $guiElem.label.length() > 0)
|
||||
## special fields: display some text only
|
||||
#set ($class = "form-group")
|
||||
#if ($guiElem.type == "error")
|
||||
#set ($class = "$class has-error")
|
||||
#end
|
||||
<div class="$class">
|
||||
<div class="col-sm-offset-3 col-sm-6">
|
||||
<span class="help-block small" id="$guiElem.name">
|
||||
$guiElem.label
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
#end
|
||||
|
||||
#elseif ($guiElem.type == "hidden" && $guiElem.name == "saml.logoutURLs")
|
||||
<script>
|
||||
var sp_urls = '$guiElem.value'.split(',');
|
||||
var final_url = '$gui.getGuiElem("saml.logoutURL").value';
|
||||
function kill_session() {
|
||||
var current_url = window.location.href;
|
||||
if (current_url.indexOf('?logout') == -1 && current_url.indexOf('&logout') == -1) {
|
||||
console.log("current URL does not terminate the IDP session");
|
||||
var logout_url = '';
|
||||
if (current_url.indexOf('?') > 0) {
|
||||
logout_url = current_url + "&logout";
|
||||
}
|
||||
else {
|
||||
logout_url = current_url + "?logout";
|
||||
}
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: logout_url,
|
||||
async: false,
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
dataType: "text",
|
||||
success: function() {},
|
||||
error: function() {}
|
||||
});
|
||||
}
|
||||
}
|
||||
var request_urls = sp_urls.filter(function(current_url) {
|
||||
return current_url.indexOf('SAMLRequest') > 0;
|
||||
});
|
||||
var response_urls = sp_urls.filter(function(current_url) {
|
||||
return current_url.indexOf('SAMLResponse') > 0;
|
||||
});
|
||||
function end_logout() {
|
||||
if (response_urls.length == 0) {
|
||||
console.log('IDP-initiated SAML logout detected');
|
||||
kill_session(); // required to terminate IDP session
|
||||
window.location.href = final_url;
|
||||
}
|
||||
else {
|
||||
console.log('SP-initiated SAML logout detected');
|
||||
kill_session(); // required to terminate IDP session
|
||||
window.location.href = response_urls[0]; // only 1 such URL allowed. process ends on SP
|
||||
}
|
||||
}
|
||||
var requests = [];
|
||||
for (var i = 0; i < request_urls.length; i++) {
|
||||
var current_url = request_urls[i];
|
||||
requests.push($.ajax({
|
||||
type: "GET",
|
||||
url: current_url,
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
crossDomain: true,
|
||||
dataType: 'jsonp',
|
||||
error: function() {}
|
||||
})
|
||||
);
|
||||
}
|
||||
// send out the requests in parallel and afterwards terminate the logout process
|
||||
// we have to terminate the logout no mather if the requests were successful or if there were failed requests
|
||||
$.when.apply($, requests).then(function() { end_logout(); }, function() { end_logout(); });
|
||||
</script>
|
||||
|
||||
#elseif ($guiElem.type == "hidden")
|
||||
<input type="hidden" name="$guiElem.name" value="$utils.escapeHtml($guiElem.value)">
|
||||
|
||||
|
||||
#else ## not info, error, button, submit, reset or hidden -> normal visual element
|
||||
|
||||
## define CSS class of representation in form
|
||||
#set ($class = "form-group")
|
||||
#if ($guiElem.optional)
|
||||
#set ($class = "$class optional")
|
||||
#else
|
||||
#set ($class = "$class required")
|
||||
#end
|
||||
|
||||
## highlight failed input validation, if flagged
|
||||
|
||||
#if ($guiElem.validationFailed && $guiElem.value && $guiElem.value.length() > 0)
|
||||
#set ($class = "$class has-error")
|
||||
#end
|
||||
|
||||
#if ($guiElem.validationFailed && (!$guiElem.value || $guiElem.value.length() == 0))
|
||||
#set ($class = "$class has-error")
|
||||
#end
|
||||
|
||||
|
||||
## the form field's container, a label, and optionally a validation-related message
|
||||
|
||||
<div class="$class">
|
||||
## Special handling required for radios + checkboxes
|
||||
#if ($guiElem.type != "radio" && $guiElem.type != "checkbox")
|
||||
|
||||
<label class="col-sm-3 control-label" for="$guiElem.name">
|
||||
#if ($guiElem.name.startsWith("inputField") && !$guiElem.optional)
|
||||
$guiElem.label<span style="color: red">*</span>
|
||||
#else
|
||||
$guiElem.label
|
||||
#end
|
||||
</label>
|
||||
|
||||
<div class="col-sm-6">
|
||||
#if ($guiElem.type == "text")
|
||||
<input class="form-control" type="text" name="$guiElem.name" id="$guiElem.name"
|
||||
maxlength="$guiElem.length"
|
||||
value="$utils.escapeHtml($guiElem.value)" tabindex="$tabindex">
|
||||
|
||||
#elseif ($guiElem.type == "pw-text")
|
||||
<div class="icon-inside">
|
||||
<input name="${guiElem.name}" type="password" class="form-control" id="${guiElem.name}" value="$utils.escapeHtml($guiElem.value)" tabindex="$tabindex">
|
||||
<button class="icon-button" type="button" onclick="toggleInputType('${guiElem.name}', '${guiElem.name}eye-icon', '${login.appDataPath}')">
|
||||
<img id="${guiElem.name}eye-icon" src="${login.appDataPath}/resources/eye.svg">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
#elseif ($guiElem.type == "select")
|
||||
#set ($scrollSize = $guiElem.getGuiElems().size())
|
||||
#set ($scrollSize = $math.min($scrollSize,4))
|
||||
#if ($guiElem.multiple)
|
||||
<select name="$guiElem.name" class="form-control" size="$scrollSize" multiple>
|
||||
#else
|
||||
<select name="$guiElem.name" class="form-control">
|
||||
#end
|
||||
#foreach ($option in $guiElem.getGuiElems())
|
||||
#if ($option.selected)
|
||||
<option value="$utils.escapeHtml($option.value)" selected>$option.label</option>
|
||||
#else
|
||||
<option value="$utils.escapeHtml($option.value)">$option.label</option>
|
||||
#end
|
||||
#end ## foreach option
|
||||
</select>
|
||||
|
||||
#elseif ($guiElem.type == "image" )
|
||||
<img src="$utils.escapeHtml($guiElem.value)" alt="$guiElem.label" />
|
||||
#end
|
||||
|
||||
#if ($guiElem.validationMessage && $guiElem.validationMessage.length() > 0)
|
||||
<span class="help-block small">$guiElem.validationMessage</span>
|
||||
#end
|
||||
|
||||
#if ($jsValidation)
|
||||
#renderElementValidation($guiElem, $gui)
|
||||
#end
|
||||
</div>
|
||||
#else
|
||||
## Special handling for checkboxes and radios
|
||||
<div class="col-sm-offset-3 col-sm-6">
|
||||
<label>
|
||||
<input type="$guiElem.type" name="$guiElem.name"
|
||||
value="$utils.escapeHtml($guiElem.value)"
|
||||
#if ($guiElem.checked || $guiElem.value == 'true')
|
||||
checked
|
||||
#end
|
||||
tabindex="$tabindex">
|
||||
$guiElem.label
|
||||
</label>
|
||||
|
||||
#if ($guiElem.validationMessage && $guiElem.validationMessage.length() > 0)
|
||||
<span class="help-block small">$guiElem.validationMessage</span>
|
||||
#end
|
||||
|
||||
#if ($jsValidation)
|
||||
#renderElementValidation($guiElem, $gui)
|
||||
#end
|
||||
</div>
|
||||
#end
|
||||
</div>
|
||||
#end
|
||||
|
||||
#end ## end macro
|
||||
|
||||
|
||||
|
||||
|
||||
#macro(renderElementValidation $guiElem, $gui)
|
||||
#if (($guiElem.validation && $guiElem.validation.length() > 0)||($guiElem.format && $guiElem.format.length() > 0))
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
#if ($guiElem.validation && $guiElem.validation.length() > 0)
|
||||
#if ($guiElem.validation.indexof('return ') > 0)
|
||||
#set ($validationFunc="function () { $guiElem.validation }")
|
||||
#else
|
||||
#set ($validationFunc="function () { return $guiElem.validation ; }")
|
||||
#end
|
||||
#else
|
||||
#set ($validationFunc="function () { return true; }")
|
||||
#end
|
||||
|
||||
var form = document.getElementById('${gui.name}');
|
||||
var formInput = form.elements["${guiElem.name}"];
|
||||
formInput.onchange = function () {
|
||||
var valid = ${validationFunc}.call(this);
|
||||
#if ($guiElem.format && $guiElem.format.length() > 0)
|
||||
valid = valid && (/${guiElem.format}/).test(this.value);
|
||||
#end
|
||||
var parent = this.parentNode;
|
||||
if (!valid) {
|
||||
parent.className += " has-error";
|
||||
} else {
|
||||
parent.className = parent.className.replace(/ has-error/g, '');
|
||||
}
|
||||
|
||||
#if (!$guiElem.optional)
|
||||
if (!this.value) {
|
||||
parent.className += " has-warning";
|
||||
} else {
|
||||
parent.className = parent.className.replace(/ has-warning/g,'');
|
||||
}
|
||||
#end
|
||||
};
|
||||
</script>
|
||||
|
||||
#end
|
||||
#end ## macro
|
||||
|
||||
|
||||
#macro(renderFormLinks $gui)
|
||||
#set ($noLinks = true)
|
||||
#foreach ($guiElem in $gui.getGuiElems())
|
||||
#if ($guiElem.type == "link")
|
||||
#if ($noLinks)
|
||||
<div class="form-group text-center">
|
||||
#set ($noLinks = false)
|
||||
#end
|
||||
<a class="link" title="${utils.escapeHtml($guiElem.label)}" href="$utils.escapeHtml($guiElem.value)">${utils.escapeHtml($guiElem.label)}</a>
|
||||
#end
|
||||
#end
|
||||
#if (!$noLinks)
|
||||
</div>
|
||||
#end
|
||||
#end
|
||||
|
||||
#macro(renderFormControls $gui)
|
||||
<div class="form-group text-center">
|
||||
#set ($buttonClass = "btn")
|
||||
#if ($isFormRequest)
|
||||
#set ($buttonClass = "$buttonClass btn-default")
|
||||
#else
|
||||
#set ($buttonClass = "$buttonClass btn-primary")
|
||||
#end
|
||||
#foreach ($guiElem in $gui.getGuiElems())
|
||||
#if ($guiElem.type == "submit" || $guiElem.type == "button" || $guiElem.type == "reset")
|
||||
<button class="$buttonClass $guiElem.cssClass"
|
||||
## special handling for button which execute a JS
|
||||
#if ($guiElem.name == 'onclick')
|
||||
type="button"
|
||||
onClick="start()"
|
||||
#else
|
||||
name="$guiElem.name"
|
||||
value="$utils.escapeHtml($guiElem.value)"
|
||||
#end
|
||||
>
|
||||
#if ($guiElem.icon != "")
|
||||
#if ($guiElem.icon.contains("http"))
|
||||
<img src="$guiElem.icon" class="$guiElem.iconCssClass" />
|
||||
#else
|
||||
<img src="${login.appDataPath}/resources/$guiElem.icon" class="$guiElem.iconCssClass" />
|
||||
#end
|
||||
#end
|
||||
$utils.escapeHtml($guiElem.label)
|
||||
</button>
|
||||
#end
|
||||
#end ## foreach
|
||||
</div>
|
||||
|
||||
#end ## end macro
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
# wait at most 200 seconds for the port to be open
|
||||
_waitInterval=10
|
||||
_waitMax=200
|
||||
_waitTime=0
|
||||
health_ok=1
|
||||
while [ $_waitTime -lt $_waitMax ]; do
|
||||
# api to check status of nevisAuth
|
||||
if [[ `eval "2>/dev/null>/dev/tcp/0.0.0.0/8988 && echo 1"` -eq 0 ]]; then
|
||||
sleep $_waitInterval
|
||||
_waitTime=$((_waitTime + _waitInterval))
|
||||
else
|
||||
health_ok=0
|
||||
break;
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $health_ok -eq 1 ]; then
|
||||
echo "timeout (200s) reached waiting for nevisLogrend (http(s)://0.0.0.0:8988)"
|
||||
exit ${health_ok}
|
||||
fi
|
|
@ -9,11 +9,11 @@ metadata:
|
|||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "92e282d1dc2b69d9e4f91fc0"
|
||||
spec:
|
||||
cn: "test.ch"
|
||||
cn: "cossa.agov-w.azure.adnovum.net"
|
||||
usage: "<reserved for future use>"
|
||||
san:
|
||||
dns:
|
||||
- "npi"
|
||||
- "npi.adn-postit-tknxchng-01-dev"
|
||||
- "test.ch"
|
||||
- "cossa.agov-w.azure.adnovum.net"
|
||||
email: []
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
apiVersion: "operator.nevis-security.ch/v1"
|
||||
kind: "NevisKeyStore"
|
||||
metadata:
|
||||
name: "npi-3b41ca4ca1c4028117d16f5b"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
||||
labels:
|
||||
deploymentTarget: "npi"
|
||||
annotations:
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "92e282d1dc2b69d9e4f91fc0"
|
||||
spec:
|
||||
cn: "klp.agov-w.azure.adnovum.net"
|
||||
usage: "<reserved for future use>"
|
||||
san:
|
||||
dns:
|
||||
- "npi"
|
||||
- "npi.adn-postit-tknxchng-01-dev"
|
||||
- "klp.agov-w.azure.adnovum.net"
|
||||
email: []
|
|
@ -46,11 +46,16 @@ spec:
|
|||
podDisruptionBudget:
|
||||
maxUnavailable: "50%"
|
||||
git:
|
||||
tag: "r-ece8a81b6d5522367837901bb038eac5dc95669e"
|
||||
tag: "r-0b41ca6ec8b7dd7ee68a9f8207d07d9aa40564eb"
|
||||
dir: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT/DEFAULT-ADN-POST-IAM-TKNXCHNG-INV/npi"
|
||||
credentials: "git-credentials"
|
||||
keystores:
|
||||
- "npi-3b41ca4ca1c4028117d16f5b"
|
||||
- "npi-23dc4a9fcc79a12d82662747"
|
||||
- "npi-cossa-realm-identity"
|
||||
truststores:
|
||||
- "npi-cossa-realm-signer-trust"
|
||||
- "npi-cossa-realm-tls-trust"
|
||||
ingresses:
|
||||
- "npi"
|
||||
podSecurity:
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: "operator.nevis-security.ch/v1"
|
||||
kind: "NevisKeyStore"
|
||||
metadata:
|
||||
name: "npi-cossa-realm-identity"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
||||
labels:
|
||||
deploymentTarget: "npi"
|
||||
annotations:
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "92e282d1dc2b69d9e4f91fc0"
|
||||
spec:
|
||||
cn: "npi"
|
||||
usage: "<reserved for future use>"
|
||||
san:
|
||||
dns:
|
||||
- "npi"
|
||||
- "npi.adn-postit-tknxchng-01-dev"
|
||||
email: []
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: "operator.nevis-security.ch/v1"
|
||||
kind: "NevisTrustStore"
|
||||
metadata:
|
||||
name: "npi-cossa-realm-signer-trust"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
||||
labels:
|
||||
deploymentTarget: "npi"
|
||||
annotations:
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "92e282d1dc2b69d9e4f91fc0"
|
||||
spec:
|
||||
keystores:
|
||||
- name: "nai-sh4r3d-default-default-signer"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: "operator.nevis-security.ch/v1"
|
||||
kind: "NevisTrustStore"
|
||||
metadata:
|
||||
name: "npi-cossa-realm-tls-trust"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
||||
labels:
|
||||
deploymentTarget: "npi"
|
||||
annotations:
|
||||
projectKey: "DEFAULT-ADN-POST-IAM-TKNXCHNG-PROJECT"
|
||||
patternId: "92e282d1dc2b69d9e4f91fc0"
|
||||
spec:
|
||||
keystores:
|
||||
- name: "nai-default-identity"
|
||||
namespace: "adn-postit-tknxchng-01-dev"
|
|
@ -10,7 +10,11 @@ metadata:
|
|||
patternId: "92e282d1dc2b69d9e4f91fc0"
|
||||
spec:
|
||||
hosts:
|
||||
- host: "test.ch"
|
||||
- host: "cossa.agov-w.azure.adnovum.net"
|
||||
protocol: "HTTPS"
|
||||
servicePort: 8443
|
||||
serviceName: "npi"
|
||||
- host: "klp.agov-w.azure.adnovum.net"
|
||||
protocol: "HTTPS"
|
||||
servicePort: 8443
|
||||
serviceName: "npi"
|
||||
|
|
|
@ -13,6 +13,7 @@ instance:
|
|||
ports:
|
||||
- "0.0.0.0:11080"
|
||||
- "0.0.0.0:8443"
|
||||
- "0.0.0.0:8443"
|
||||
control:
|
||||
start: "systemctl restart nevisproxy@default"
|
||||
stop: "systemctl stop nevisproxy@default"
|
||||
|
|
|
@ -12,10 +12,15 @@
|
|||
<!-- source: pattern://92e282d1dc2b69d9e4f91fc0 -->
|
||||
<Connector port="11080" name="management" listen="0.0.0.0:11080"/>
|
||||
<!-- source: pattern://23dc4a9fcc79a12d82662747 -->
|
||||
<Connector port="443" name="test.ch" listen="0.0.0.0:8443">
|
||||
<Connector port="443" name="cossa.agov-w.azure.adnovum.net" listen="0.0.0.0:8443">
|
||||
<!-- source: pattern://23dc4a9fcc79a12d82662747, pattern://23dc4a9fcc79a12d82662747#keystore -->
|
||||
<SSL SSLCipherSuite="ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256" SSLOptions="+OptRenegotiate +StdEnvVars +ExportCertData" SSLProtocol="-all +TLSv1.2 -TLSv1.3" SSLCertificateFile="/var/opt/keys/own/npi-23dc4a9fcc79a12d82662747/cert.pem" SSLCertificateKeyFile="/var/opt/keys/own/npi-23dc4a9fcc79a12d82662747/key.pem" SSLCertificateChainFile="/var/opt/keys/own/npi-23dc4a9fcc79a12d82662747/ca-chain.pem" SSLInsecureRenegotiation="off" SSLHonorCipherOrder="on"/>
|
||||
</Connector>
|
||||
<!-- source: pattern://3b41ca4ca1c4028117d16f5b -->
|
||||
<Connector port="443" name="klp.agov-w.azure.adnovum.net" nameVirtualHost="0.0.0.0:8443">
|
||||
<!-- source: pattern://3b41ca4ca1c4028117d16f5b, pattern://3b41ca4ca1c4028117d16f5b#keystore -->
|
||||
<SSL SSLCipherSuite="ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256" SSLOptions="+OptRenegotiate +StdEnvVars +ExportCertData" SSLProtocol="-all +TLSv1.2 -TLSv1.3" SSLCertificateFile="/var/opt/keys/own/npi-3b41ca4ca1c4028117d16f5b/cert.pem" SSLCertificateKeyFile="/var/opt/keys/own/npi-3b41ca4ca1c4028117d16f5b/key.pem" SSLCertificateChainFile="/var/opt/keys/own/npi-3b41ca4ca1c4028117d16f5b/ca-chain.pem" SSLInsecureRenegotiation="off" SSLHonorCipherOrder="on"/>
|
||||
</Connector>
|
||||
<!-- source: pattern://92e282d1dc2b69d9e4f91fc0 -->
|
||||
<Engine defaultHost="management">
|
||||
<!-- source: pattern://92e282d1dc2b69d9e4f91fc0 -->
|
||||
|
@ -24,9 +29,14 @@
|
|||
<Context docBase="/var/opt/nevisproxy/default/host-management"/>
|
||||
</Host>
|
||||
<!-- source: pattern://92e282d1dc2b69d9e4f91fc0 -->
|
||||
<Host name="test.ch">
|
||||
<Host name="cossa.agov-w.azure.adnovum.net">
|
||||
<!-- source: pattern://23dc4a9fcc79a12d82662747 -->
|
||||
<Context entryURI="/" unsecureConnection="allow" trailingSlashRedirect="true" filePreload="false" docBase="/var/opt/nevisproxy/default/host-test.ch" path="" allowedMethods="ALL-HTTP ALL-WEBDAV -TRACE -CONNECT"/>
|
||||
<Context entryURI="/" unsecureConnection="allow" trailingSlashRedirect="true" filePreload="false" docBase="/var/opt/nevisproxy/default/host-cossa.agov-w.azure.adnovum.net" path="" allowedMethods="ALL-HTTP ALL-WEBDAV -TRACE -CONNECT"/>
|
||||
</Host>
|
||||
<!-- source: pattern://92e282d1dc2b69d9e4f91fc0 -->
|
||||
<Host name="klp.agov-w.azure.adnovum.net">
|
||||
<!-- source: pattern://3b41ca4ca1c4028117d16f5b -->
|
||||
<Context entryURI="/" unsecureConnection="allow" trailingSlashRedirect="true" filePreload="false" docBase="/var/opt/nevisproxy/default/host-klp.agov-w.azure.adnovum.net" path="" allowedMethods="ALL-HTTP ALL-WEBDAV -TRACE -CONNECT"/>
|
||||
</Host>
|
||||
</Engine>
|
||||
</Service>
|
||||
|
|
|
@ -0,0 +1,870 @@
|
|||
# ------------------------------------------------------------------------
|
||||
# OWASP ModSecurity Core Rule Set ver.3.3.5
|
||||
# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
|
||||
# Copyright (c) 2021-2023 Core Rule Set project. All rights reserved.
|
||||
#
|
||||
# The OWASP ModSecurity Core Rule Set is distributed under
|
||||
# Apache Software License (ASL) version 2
|
||||
# Please see the enclosed LICENSE file for full details.
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Introduction ]] --------------------------------------------------------
|
||||
#
|
||||
# The OWASP ModSecurity Core Rule Set (CRS) is a set of generic attack
|
||||
# detection rules that provide a base level of protection for any web
|
||||
# application. They are written for the open source, cross-platform
|
||||
# ModSecurity Web Application Firewall.
|
||||
#
|
||||
# See also:
|
||||
# https://coreruleset.org/
|
||||
# https://github.com/SpiderLabs/owasp-modsecurity-crs
|
||||
# https://www.owasp.org/index.php/Category:OWASP_ModSecurity_Core_Rule_Set_Project
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
# -- [[ System Requirements ]] -------------------------------------------------
|
||||
#
|
||||
# CRS requires ModSecurity version 2.8.0 or above.
|
||||
# We recommend to always use the newest ModSecurity version.
|
||||
#
|
||||
# The configuration directives/settings in this file are used to control
|
||||
# the OWASP ModSecurity CRS. These settings do **NOT** configure the main
|
||||
# ModSecurity settings (modsecurity.conf) such as SecRuleEngine,
|
||||
# SecRequestBodyAccess, SecAuditEngine, SecDebugLog, and XML processing.
|
||||
#
|
||||
# The CRS assumes that modsecurity.conf has been loaded. It is bundled with
|
||||
# ModSecurity. If you don't have it, you can get it from:
|
||||
# 2.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v2/master/modsecurity.conf-recommended
|
||||
# 3.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
|
||||
#
|
||||
# The order of file inclusion in your webserver configuration should always be:
|
||||
# 1. modsecurity.conf
|
||||
# 2. crs-setup.conf (this file)
|
||||
# 3. rules/*.conf (the CRS rule files)
|
||||
#
|
||||
# Please refer to the INSTALL file for detailed installation instructions.
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Mode of Operation: Anomaly Scoring vs. Self-Contained ]] ---------------
|
||||
#
|
||||
# The CRS can run in two modes:
|
||||
#
|
||||
# -- [[ Anomaly Scoring Mode (default) ]] --
|
||||
# In CRS3, anomaly mode is the default and recommended mode, since it gives the
|
||||
# most accurate log information and offers the most flexibility in setting your
|
||||
# blocking policies. It is also called "collaborative detection mode".
|
||||
# In this mode, each matching rule increases an 'anomaly score'.
|
||||
# At the conclusion of the inbound rules, and again at the conclusion of the
|
||||
# outbound rules, the anomaly score is checked, and the blocking evaluation
|
||||
# rules apply a disruptive action, by default returning an error 403.
|
||||
#
|
||||
# -- [[ Self-Contained Mode ]] --
|
||||
# In this mode, rules apply an action instantly. This was the CRS2 default.
|
||||
# It can lower resource usage, at the cost of less flexibility in blocking policy
|
||||
# and less informative audit logs (only the first detected threat is logged).
|
||||
# Rules inherit the disruptive action that you specify (i.e. deny, drop, etc).
|
||||
# The first rule that matches will execute this action. In most cases this will
|
||||
# cause evaluation to stop after the first rule has matched, similar to how many
|
||||
# IDSs function.
|
||||
#
|
||||
# -- [[ Alert Logging Control ]] --
|
||||
# In the mode configuration, you must also adjust the desired logging options.
|
||||
# There are three common options for dealing with logging. By default CRS enables
|
||||
# logging to the webserver error log (or Event viewer) plus detailed logging to
|
||||
# the ModSecurity audit log (configured under SecAuditLog in modsecurity.conf).
|
||||
#
|
||||
# - To log to both error log and ModSecurity audit log file, use: "log,auditlog"
|
||||
# - To log *only* to the ModSecurity audit log file, use: "nolog,auditlog"
|
||||
# - To log *only* to the error log file, use: "log,noauditlog"
|
||||
#
|
||||
# Examples for the various modes follow.
|
||||
# You must leave one of the following options enabled.
|
||||
# Note that you must specify the same line for phase:1 and phase:2.
|
||||
#
|
||||
|
||||
# Default: Anomaly Scoring mode, log to error log, log to ModSecurity audit log
|
||||
# - By default, offending requests are blocked with an error 403 response.
|
||||
# - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
|
||||
# and review section 'Changing the Disruptive Action for Anomaly Mode'.
|
||||
# - In Apache, you can use ErrorDocument to show a friendly error page or
|
||||
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
|
||||
#
|
||||
SecDefaultAction "phase:1,log,auditlog,pass"
|
||||
SecDefaultAction "phase:2,log,auditlog,pass"
|
||||
|
||||
# Example: Anomaly Scoring mode, log only to ModSecurity audit log
|
||||
# - By default, offending requests are blocked with an error 403 response.
|
||||
# - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
|
||||
# and review section 'Changing the Disruptive Action for Anomaly Mode'.
|
||||
# - In Apache, you can use ErrorDocument to show a friendly error page or
|
||||
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
|
||||
#
|
||||
# SecDefaultAction "phase:1,nolog,auditlog,pass"
|
||||
# SecDefaultAction "phase:2,nolog,auditlog,pass"
|
||||
|
||||
# Example: Self-contained mode, return error 403 on blocking
|
||||
# - In this configuration the default disruptive action becomes 'deny'. After a
|
||||
# rule triggers, it will stop processing the request and return an error 403.
|
||||
# - You can also use a different error status, such as 404, 406, et cetera.
|
||||
# - In Apache, you can use ErrorDocument to show a friendly error page or
|
||||
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
|
||||
#
|
||||
# SecDefaultAction "phase:1,log,auditlog,deny,status:403"
|
||||
# SecDefaultAction "phase:2,log,auditlog,deny,status:403"
|
||||
|
||||
# Example: Self-contained mode, redirect back to homepage on blocking
|
||||
# - In this configuration the 'tag' action includes the Host header data in the
|
||||
# log. This helps to identify which virtual host triggered the rule (if any).
|
||||
# - Note that this might cause redirect loops in some situations; for example
|
||||
# if a Cookie or User-Agent header is blocked, it will also be blocked when
|
||||
# the client subsequently tries to access the homepage. You can also redirect
|
||||
# to another custom URL.
|
||||
# SecDefaultAction "phase:1,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'"
|
||||
# SecDefaultAction "phase:2,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Paranoia Level Initialization ]] ---------------------------------------
|
||||
#
|
||||
# The Paranoia Level (PL) setting allows you to choose the desired level
|
||||
# of rule checks that will add to your anomaly scores.
|
||||
#
|
||||
# With each paranoia level increase, the CRS enables additional rules
|
||||
# giving you a higher level of security. However, higher paranoia levels
|
||||
# also increase the possibility of blocking some legitimate traffic due to
|
||||
# false alarms (also named false positives or FPs). If you use higher
|
||||
# paranoia levels, it is likely that you will need to add some exclusion
|
||||
# rules for certain requests and applications receiving complex input.
|
||||
#
|
||||
# - A paranoia level of 1 is default. In this level, most core rules
|
||||
# are enabled. PL1 is advised for beginners, installations
|
||||
# covering many different sites and applications, and for setups
|
||||
# with standard security requirements.
|
||||
# At PL1 you should face FPs rarely. If you encounter FPs, please
|
||||
# open an issue on the CRS GitHub site and don't forget to attach your
|
||||
# complete Audit Log record for the request with the issue.
|
||||
# - Paranoia level 2 includes many extra rules, for instance enabling
|
||||
# many regexp-based SQL and XSS injection protections, and adding
|
||||
# extra keywords checked for code injections. PL2 is advised
|
||||
# for moderate to experienced users desiring more complete coverage
|
||||
# and for installations with elevated security requirements.
|
||||
# PL2 comes with some FPs which you need to handle.
|
||||
# - Paranoia level 3 enables more rules and keyword lists, and tweaks
|
||||
# limits on special characters used. PL3 is aimed at users experienced
|
||||
# at the handling of FPs and at installations with a high security
|
||||
# requirement.
|
||||
# - Paranoia level 4 further restricts special characters.
|
||||
# The highest level is advised for experienced users protecting
|
||||
# installations with very high security requirements. Running PL4 will
|
||||
# likely produce a very high number of FPs which have to be
|
||||
# treated before the site can go productive.
|
||||
#
|
||||
# All rules will log their PL to the audit log;
|
||||
# example: [tag "paranoia-level/2"]. This allows you to deduct from the
|
||||
# audit log how the WAF behavior is affected by paranoia level.
|
||||
#
|
||||
# It is important to also look into the variable
|
||||
# tx.enforce_bodyproc_urlencoded (Enforce Body Processor URLENCODED)
|
||||
# defined below. Enabling it closes a possible bypass of CRS.
|
||||
#
|
||||
# Uncomment this rule to change the default:
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900000,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.paranoia_level=1"
|
||||
|
||||
|
||||
# It is possible to execute rules from a higher paranoia level but not include
|
||||
# them in the anomaly scoring. This allows you to take a well-tuned system on
|
||||
# paranoia level 1 and add rules from paranoia level 2 without having to fear
|
||||
# the new rules would lead to false positives that raise your score above the
|
||||
# threshold.
|
||||
# This optional feature is enabled by uncommenting the following rule and
|
||||
# setting the tx.executing_paranoia_level.
|
||||
# Technically, rules up to the level defined in tx.executing_paranoia_level
|
||||
# will be executed, but only the rules up to tx.paranoia_level affect the
|
||||
# anomaly scores.
|
||||
# By default, tx.executing_paranoia_level is set to tx.paranoia_level.
|
||||
# tx.executing_paranoia_level must not be lower than tx.paranoia_level.
|
||||
#
|
||||
# Please notice that setting tx.executing_paranoia_level to a higher paranoia
|
||||
# level results in a performance impact that is equally high as setting
|
||||
# tx.paranoia_level to said level.
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900001,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.executing_paranoia_level=1"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Enforce Body Processor URLENCODED ]] -----------------------------------
|
||||
#
|
||||
# ModSecurity selects the body processor based on the Content-Type request
|
||||
# header. But clients are not always setting the Content-Type header for their
|
||||
# request body payloads. This will leave ModSecurity with limited vision into
|
||||
# the payload. The variable tx.enforce_bodyproc_urlencoded lets you force the
|
||||
# URLENCODED body processor in these situations. This is off by default, as it
|
||||
# implies a change of the behaviour of ModSecurity beyond CRS (the body
|
||||
# processor applies to all rules, not only CRS) and because it may lead to
|
||||
# false positives already on paranoia level 1. However, enabling this variable
|
||||
# closes a possible bypass of CRS so it should be considered.
|
||||
#
|
||||
# Uncomment this rule to change the default:
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900010,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.enforce_bodyproc_urlencoded=1"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Anomaly Mode Severity Levels ]] ----------------------------------------
|
||||
#
|
||||
# Each rule in the CRS has an associated severity level.
|
||||
# These are the default scoring points for each severity level.
|
||||
# These settings will be used to increment the anomaly score if a rule matches.
|
||||
# You may adjust these points to your liking, but this is usually not needed.
|
||||
#
|
||||
# - CRITICAL severity: Anomaly Score of 5.
|
||||
# Mostly generated by the application attack rules (93x and 94x files).
|
||||
# - ERROR severity: Anomaly Score of 4.
|
||||
# Generated mostly from outbound leakage rules (95x files).
|
||||
# - WARNING severity: Anomaly Score of 3.
|
||||
# Generated mostly by malicious client rules (91x files).
|
||||
# - NOTICE severity: Anomaly Score of 2.
|
||||
# Generated mostly by the protocol rules (92x files).
|
||||
#
|
||||
# In anomaly mode, these scores are cumulative.
|
||||
# So it's possible for a request to hit multiple rules.
|
||||
#
|
||||
# (Note: In this file, we use 'phase:1' to set CRS configuration variables.
|
||||
# In general, 'phase:request' is used. However, we want to make absolutely sure
|
||||
# that all configuration variables are set before the CRS rules are processed.)
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900100,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.critical_anomaly_score=5,\
|
||||
# setvar:tx.error_anomaly_score=4,\
|
||||
# setvar:tx.warning_anomaly_score=3,\
|
||||
# setvar:tx.notice_anomaly_score=2"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Anomaly Mode Blocking Threshold Levels ]] ------------------------------
|
||||
#
|
||||
# Here, you can specify at which cumulative anomaly score an inbound request,
|
||||
# or outbound response, gets blocked.
|
||||
#
|
||||
# Most detected inbound threats will give a critical score of 5.
|
||||
# Smaller violations, like violations of protocol/standards, carry lower scores.
|
||||
#
|
||||
# [ At default value ]
|
||||
# If you keep the blocking thresholds at the defaults, the CRS will work
|
||||
# similarly to previous CRS versions: a single critical rule match will cause
|
||||
# the request to be blocked and logged.
|
||||
#
|
||||
# [ Using higher values ]
|
||||
# If you want to make the CRS less sensitive, you can increase the blocking
|
||||
# thresholds, for instance to 7 (which would require multiple rule matches
|
||||
# before blocking) or 10 (which would require at least two critical alerts - or
|
||||
# a combination of many lesser alerts), or even higher. However, increasing the
|
||||
# thresholds might cause some attacks to bypass the CRS rules or your policies.
|
||||
#
|
||||
# [ New deployment strategy: Starting high and decreasing ]
|
||||
# It is a common practice to start a fresh CRS installation with elevated
|
||||
# anomaly scoring thresholds (>100) and then lower the limits as your
|
||||
# confidence in the setup grows. You may also look into the Sampling
|
||||
# Percentage section below for a different strategy to ease into a new
|
||||
# CRS installation.
|
||||
#
|
||||
# [ Anomaly Threshold / Paranoia Level Quadrant ]
|
||||
#
|
||||
# High Anomaly Limit | High Anomaly Limit
|
||||
# Low Paranoia Level | High Paranoia Level
|
||||
# -> Fresh Site | -> Experimental Site
|
||||
# ------------------------------------------------------
|
||||
# Low Anomaly Limit | Low Anomaly Limit
|
||||
# Low Paranoia Level | High Paranoia Level
|
||||
# -> Standard Site | -> High Security Site
|
||||
#
|
||||
# Uncomment this rule to change the defaults:
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900110,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.inbound_anomaly_score_threshold=5,\
|
||||
# setvar:tx.outbound_anomaly_score_threshold=4"
|
||||
|
||||
#
|
||||
# -- [[ Application Specific Rule Exclusions ]] ----------------------------------------
|
||||
#
|
||||
# Some well-known applications may undertake actions that appear to be
|
||||
# malicious. This includes actions such as allowing HTML or Javascript within
|
||||
# parameters. In such cases the CRS aims to prevent false positives by allowing
|
||||
# administrators to enable prebuilt, application specific exclusions on an
|
||||
# application by application basis.
|
||||
# These application specific exclusions are distinct from the rules that would
|
||||
# be placed in the REQUEST-900-EXCLUSION-RULES-BEFORE-CRS configuration file as
|
||||
# they are prebuilt for specific applications. The 'REQUEST-900' file is
|
||||
# designed for users to add their own custom exclusions. Note, using these
|
||||
# application specific exclusions may loosen restrictions of the CRS,
|
||||
# especially if used with an application they weren't designed for. As a result
|
||||
# they should be applied with care.
|
||||
# To use this functionality you must specify a supported application. To do so
|
||||
# uncomment rule 900130. In addition to uncommenting the rule you will need to
|
||||
# specify which application(s) you'd like to enable exclusions for. Only a
|
||||
# (very) limited set of applications are currently supported, please use the
|
||||
# filenames prefixed with 'REQUEST-903' to guide you in your selection.
|
||||
# Such filenames use the following convention:
|
||||
# REQUEST-903.9XXX-{APPNAME}-EXCLUSIONS-RULES.conf
|
||||
#
|
||||
# It is recommended if you run multiple web applications on your site to limit
|
||||
# the effects of the exclusion to only the path where the excluded webapp
|
||||
# resides using a rule similar to the following example:
|
||||
# SecRule REQUEST_URI "@beginsWith /wordpress/" setvar:tx.crs_exclusions_wordpress=1
|
||||
|
||||
#
|
||||
# Modify and uncomment this rule to select which application:
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900130,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.crs_exclusions_cpanel=1,\
|
||||
# setvar:tx.crs_exclusions_drupal=1,\
|
||||
# setvar:tx.crs_exclusions_dokuwiki=1,\
|
||||
# setvar:tx.crs_exclusions_nextcloud=1,\
|
||||
# setvar:tx.crs_exclusions_wordpress=1,\
|
||||
# setvar:tx.crs_exclusions_xenforo=1"
|
||||
|
||||
#
|
||||
# -- [[ HTTP Policy Settings ]] ------------------------------------------------
|
||||
#
|
||||
# This section defines your policies for the HTTP protocol, such as:
|
||||
# - allowed HTTP versions, HTTP methods, allowed request Content-Types
|
||||
# - forbidden file extensions (e.g. .bak, .sql) and request headers (e.g. Proxy)
|
||||
#
|
||||
# These variables are used in the following rule files:
|
||||
# - REQUEST-911-METHOD-ENFORCEMENT.conf
|
||||
# - REQUEST-912-DOS-PROTECTION.conf
|
||||
# - REQUEST-920-PROTOCOL-ENFORCEMENT.conf
|
||||
|
||||
# HTTP methods that a client is allowed to use.
|
||||
# Default: GET HEAD POST OPTIONS
|
||||
# Example: for RESTful APIs, add the following methods: PUT PATCH DELETE
|
||||
# Example: for WebDAV, add the following methods: CHECKOUT COPY DELETE LOCK
|
||||
# MERGE MKACTIVITY MKCOL MOVE PROPFIND PROPPATCH PUT UNLOCK
|
||||
# Uncomment this rule to change the default.
|
||||
# Changed by Nevis: As nevisProxy provides its own method checks we allow all methods here
|
||||
SecAction \
|
||||
"id:900200,\
|
||||
phase:1,\
|
||||
nolog,\
|
||||
pass,\
|
||||
t:none,\
|
||||
setvar:'tx.allowed_methods=GET HEAD POST OPTIONS PUT PATCH DELETE CHECKOUT COPY DELETE LOCK MERGE MKACTIVITY MKCOL MOVE PROPFIND PROPPATCH PUT UNLOCK TRACE'"
|
||||
|
||||
# Content-Types that a client is allowed to send in a request.
|
||||
# Default: |application/x-www-form-urlencoded| |multipart/form-data| |multipart/related|
|
||||
# |text/xml| |application/xml| |application/soap+xml| |application/json|
|
||||
# |application/cloudevents+json| |application/cloudevents-batch+json|
|
||||
#
|
||||
# Please note, that the rule where CRS uses this variable (920420) evaluates it with operator
|
||||
# `@within`, which is case sensitive, but uses t:lowercase. You must add your whole custom
|
||||
# Content-Type with lowercase.
|
||||
#
|
||||
# Bypass Warning: some applications may not rely on the content-type request header in order
|
||||
# to parse the request body. This could make an attacker able to send malicious URLENCODED/JSON/XML
|
||||
# payloads without being detected by the WAF. Allowing request content-type that doesn't activate any
|
||||
# body processor (for example: "text/plain", "application/x-amf", "application/octet-stream", etc..)
|
||||
# could lead to a WAF bypass. For example, a malicious JSON payload submitted with a "text/plain"
|
||||
# content type may still be interpreted as JSON by a backend application but would not trigger the
|
||||
# JSON body parser at the WAF, leading to a bypass.
|
||||
#
|
||||
# To prevent blocking request with not allowed content-type by default, you can create an exclusion
|
||||
# rule that removes rule 920420. For example:
|
||||
# SecRule REQUEST_HEADERS:Content-Type "@rx ^text/plain" \
|
||||
# "id:1234,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# ctl:ruleRemoveById=920420,\
|
||||
# chain"
|
||||
# SecRule REQUEST_URI "@rx ^/foo/bar" "t:none"
|
||||
#
|
||||
# Uncomment this rule to change the default.
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900220,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded| |multipart/form-data| |multipart/related| |text/xml| |application/xml| |application/soap+xml| |application/json| |application/cloudevents+json| |application/cloudevents-batch+json|'"
|
||||
|
||||
# Allowed HTTP versions.
|
||||
# Default: HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0
|
||||
# Example for legacy clients: HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0
|
||||
# Note that some web server versions use 'HTTP/2', some 'HTTP/2.0', so
|
||||
# we include both version strings by default.
|
||||
# Uncomment this rule to change the default.
|
||||
#SecAction \
|
||||
# "id:900230,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:'tx.allowed_http_versions=HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0'"
|
||||
|
||||
# Forbidden file extensions.
|
||||
# Guards against unintended exposure of development/configuration files.
|
||||
# Default: .asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .rdb/ .resources/ .resx/ .sql/ .swp/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/
|
||||
# Example: .bak/ .config/ .conf/ .db/ .ini/ .log/ .old/ .pass/ .pdb/ .rdb/ .sql/
|
||||
# Uncomment this rule to change the default.
|
||||
#SecAction \
|
||||
# "id:900240,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:'tx.restricted_extensions=.asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .rdb/ .resources/ .resx/ .sql/ .swp/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/'"
|
||||
|
||||
# Forbidden request headers.
|
||||
# Header names should be lowercase, enclosed by /slashes/ as delimiters.
|
||||
# Default: /accept-charset/ /content-encoding/ /proxy/ /lock-token/ /content-range/ /if/
|
||||
#
|
||||
# Note: Accept-Charset is a deprecated header that should not be used by clients and
|
||||
# ignored by servers. It can be used for a response WAF bypass, by asking for a charset
|
||||
# that the WAF cannot decode.
|
||||
# Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset
|
||||
#
|
||||
# Note: Content-Encoding is used to list any encodings that have been applied to the
|
||||
# original payload. It is only used for compression, which isn't supported by CRS by
|
||||
# default since it blocks newlines and null bytes inside the request body. Most
|
||||
# compression algorithms require at least null bytes per RFC. Blocking it shouldn't
|
||||
# break anything and increases security since ModSecurity is incapable of properly
|
||||
# scanning compressed request bodies.
|
||||
#
|
||||
# Note: Blocking Proxy header prevents 'httpoxy' vulnerability: https://httpoxy.org
|
||||
#
|
||||
# Uncomment this rule to change the default.
|
||||
#SecAction \
|
||||
# "id:900250,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:'tx.restricted_headers=/accept-charset/ /content-encoding/ /proxy/ /lock-token/ /content-range/ /if/'"
|
||||
|
||||
# File extensions considered static files.
|
||||
# Extensions include the dot, lowercase, enclosed by /slashes/ as delimiters.
|
||||
# Used in DoS protection rule. See section "Anti-Automation / DoS Protection".
|
||||
# Default: /.jpg/ /.jpeg/ /.png/ /.gif/ /.js/ /.css/ /.ico/ /.svg/ /.webp/
|
||||
# Uncomment this rule to change the default.
|
||||
#SecAction \
|
||||
# "id:900260,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:'tx.static_extensions=/.jpg/ /.jpeg/ /.png/ /.gif/ /.js/ /.css/ /.ico/ /.svg/ /.webp/'"
|
||||
|
||||
# Content-Types charsets that a client is allowed to send in a request.
|
||||
# Default: utf-8|iso-8859-1|iso-8859-15|windows-1252
|
||||
# Uncomment this rule to change the default.
|
||||
# Use "|" to separate multiple charsets like in the rule defining
|
||||
# tx.allowed_request_content_type.
|
||||
#SecAction \
|
||||
# "id:900280,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:'tx.allowed_request_content_type_charset=utf-8|iso-8859-1|iso-8859-15|windows-1252'"
|
||||
|
||||
#
|
||||
# -- [[ HTTP Argument/Upload Limits ]] -----------------------------------------
|
||||
#
|
||||
# Here you can define optional limits on HTTP get/post parameters and uploads.
|
||||
# This can help to prevent application specific DoS attacks.
|
||||
#
|
||||
# These values are checked in REQUEST-920-PROTOCOL-ENFORCEMENT.conf.
|
||||
# Beware of blocking legitimate traffic when enabling these limits.
|
||||
#
|
||||
|
||||
# Block request if number of arguments is too high
|
||||
# Default: unlimited
|
||||
# Example: 255
|
||||
# Uncomment this rule to set a limit.
|
||||
#SecAction \
|
||||
# "id:900300,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.max_num_args=255"
|
||||
|
||||
# Block request if the length of any argument name is too high
|
||||
# Default: unlimited
|
||||
# Example: 100
|
||||
# Uncomment this rule to set a limit.
|
||||
#SecAction \
|
||||
# "id:900310,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.arg_name_length=100"
|
||||
|
||||
# Block request if the length of any argument value is too high
|
||||
# Default: unlimited
|
||||
# Example: 400
|
||||
# Uncomment this rule to set a limit.
|
||||
#SecAction \
|
||||
# "id:900320,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.arg_length=400"
|
||||
|
||||
# Block request if the total length of all combined arguments is too high
|
||||
# Default: unlimited
|
||||
# Example: 64000
|
||||
# Uncomment this rule to set a limit.
|
||||
#SecAction \
|
||||
# "id:900330,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.total_arg_length=64000"
|
||||
|
||||
# Block request if the file size of any individual uploaded file is too high
|
||||
# Default: unlimited
|
||||
# Example: 1048576
|
||||
# Uncomment this rule to set a limit.
|
||||
#SecAction \
|
||||
# "id:900340,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.max_file_size=1048576"
|
||||
|
||||
# Block request if the total size of all combined uploaded files is too high
|
||||
# Default: unlimited
|
||||
# Example: 1048576
|
||||
# Uncomment this rule to set a limit.
|
||||
#SecAction \
|
||||
# "id:900350,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.combined_file_sizes=1048576"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Easing In / Sampling Percentage ]] -------------------------------------
|
||||
#
|
||||
# Adding the Core Rule Set to an existing productive site can lead to false
|
||||
# positives, unexpected performance issues and other undesired side effects.
|
||||
#
|
||||
# It can be beneficial to test the water first by enabling the CRS for a
|
||||
# limited number of requests only and then, when you have solved the issues (if
|
||||
# any) and you have confidence in the setup, to raise the ratio of requests
|
||||
# being sent into the ruleset.
|
||||
#
|
||||
# Adjust the percentage of requests that are funnelled into the Core Rules by
|
||||
# setting TX.sampling_percentage below. The default is 100, meaning that every
|
||||
# request gets checked by the CRS. The selection of requests, which are going
|
||||
# to be checked, is based on a pseudo random number generated by ModSecurity.
|
||||
#
|
||||
# If a request is allowed to pass without being checked by the CRS, there is no
|
||||
# entry in the audit log (for performance reasons), but an error log entry is
|
||||
# written. If you want to disable the error log entry, then issue the
|
||||
# following directive somewhere after the inclusion of the CRS
|
||||
# (E.g., RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf).
|
||||
#
|
||||
# SecRuleUpdateActionById 901150 "nolog"
|
||||
#
|
||||
# ATTENTION: If this TX.sampling_percentage is below 100, then some of the
|
||||
# requests will bypass the Core Rules completely and you lose the ability to
|
||||
# protect your service with ModSecurity.
|
||||
#
|
||||
# Uncomment this rule to enable this feature:
|
||||
#
|
||||
#SecAction "id:900400,\
|
||||
# phase:1,\
|
||||
# pass,\
|
||||
# nolog,\
|
||||
# setvar:tx.sampling_percentage=100"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Project Honey Pot HTTP Blacklist ]] ------------------------------------
|
||||
#
|
||||
# Optionally, you can check the client IP address against the Project Honey Pot
|
||||
# HTTPBL (dnsbl.httpbl.org). In order to use this, you need to register to get a
|
||||
# free API key. Set it here with SecHttpBlKey.
|
||||
#
|
||||
# Project Honeypot returns multiple different malicious IP types.
|
||||
# You may specify which you want to block by enabling or disabling them below.
|
||||
#
|
||||
# Ref: https://www.projecthoneypot.org/httpbl.php
|
||||
# Ref: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#wiki-SecHttpBlKey
|
||||
#
|
||||
# Uncomment these rules to use this feature:
|
||||
#
|
||||
#SecHttpBlKey XXXXXXXXXXXXXXXXX
|
||||
#SecAction "id:900500,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.block_search_ip=1,\
|
||||
# setvar:tx.block_suspicious_ip=1,\
|
||||
# setvar:tx.block_harvester_ip=1,\
|
||||
# setvar:tx.block_spammer_ip=1"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ GeoIP Database ]] ------------------------------------------------------
|
||||
#
|
||||
# There are some rulesets that inspect geolocation data of the client IP address
|
||||
# (geoLookup). The CRS uses geoLookup to implement optional country blocking.
|
||||
#
|
||||
# To use geolocation, we make use of the MaxMind GeoIP database.
|
||||
# This database is not included with the CRS and must be downloaded.
|
||||
#
|
||||
# There are two formats for the GeoIP database. ModSecurity v2 uses GeoLite (.dat files),
|
||||
# and ModSecurity v3 uses GeoLite2 (.mmdb files).
|
||||
#
|
||||
# If you use ModSecurity 3, MaxMind provides a binary for updating GeoLite2 files,
|
||||
# see https://github.com/maxmind/geoipupdate.
|
||||
#
|
||||
# Download the package for your OS, and read https://dev.maxmind.com/geoip/geoipupdate/
|
||||
# for configuration options.
|
||||
#
|
||||
# Warning: GeoLite (not GeoLite2) databases are considered legacy, and not being updated anymore.
|
||||
# See https://support.maxmind.com/geolite-legacy-discontinuation-notice/ for more info.
|
||||
#
|
||||
# Therefore, if you use ModSecurity v2, you need to regenerate updated .dat files
|
||||
# from CSV files first.
|
||||
#
|
||||
# You can achieve this using https://github.com/sherpya/geolite2legacy
|
||||
# Pick the zip files from maxmind site:
|
||||
# https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country-CSV.zip
|
||||
#
|
||||
# Follow the guidelines for installing the tool and run:
|
||||
# ./geolite2legacy.py -i GeoLite2-Country-CSV.zip \
|
||||
# -f geoname2fips.csv -o /usr/share/GeoliteCountry.dat
|
||||
#
|
||||
# Update the database regularly, see Step 3 of the configuration link above.
|
||||
#
|
||||
# By default, when you execute `sudo geoipupdate` on Linux, files from the free database
|
||||
# will be downloaded to `/usr/share/GeoIP` (both v1 and v2).
|
||||
#
|
||||
# Then choose from:
|
||||
# - `GeoLite2-Country.mmdb` (if you are using ModSecurity v3)
|
||||
# - `GeoLiteCountry.dat` (if you are using ModSecurity v2)
|
||||
#
|
||||
# Ref: http://blog.spiderlabs.com/2010/10/detecting-malice-with-modsecurity-geolocation-data.html
|
||||
# Ref: http://blog.spiderlabs.com/2010/11/detecting-malice-with-modsecurity-ip-forensics.html
|
||||
#
|
||||
# Uncomment only one of the next rules here to use this feature.
|
||||
# Choose the one depending on the ModSecurity version you are using, and change the path accordingly:
|
||||
#
|
||||
# For ModSecurity v3:
|
||||
#SecGeoLookupDB /usr/share/GeoIP/GeoLite2-Country.mmdb
|
||||
# For ModSecurity v2 (points to the converted one):
|
||||
#SecGeoLookupDB /usr/share/GeoIP/GeoLiteCountry.dat
|
||||
|
||||
#
|
||||
# -=[ Block Countries ]=-
|
||||
#
|
||||
# Rules in the IP Reputation file can check the client against a list of high
|
||||
# risk country codes. These countries have to be defined in the variable
|
||||
# tx.high_risk_country_codes via their ISO 3166 two-letter country code:
|
||||
# https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements
|
||||
#
|
||||
# If you are sure that you are not getting any legitimate requests from a given
|
||||
# country, then you can disable all access from that country via this variable.
|
||||
# The rule performing the test has the rule id 910100.
|
||||
#
|
||||
# This rule requires SecGeoLookupDB to be enabled and the GeoIP database to be
|
||||
# downloaded (see the section "GeoIP Database" above.)
|
||||
#
|
||||
# By default, the list is empty. A list used by some sites was the following:
|
||||
# setvar:'tx.high_risk_country_codes=UA ID YU LT EG RO BG TR RU PK MY CN'"
|
||||
#
|
||||
# Uncomment this rule to use this feature:
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900600,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:'tx.high_risk_country_codes='"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Anti-Automation / DoS Protection ]] ------------------------------------
|
||||
#
|
||||
# Optional DoS protection against clients making requests too quickly.
|
||||
#
|
||||
# When a client is making more than 100 requests (excluding static files) within
|
||||
# 60 seconds, this is considered a 'burst'. After two bursts, the client is
|
||||
# blocked for 600 seconds.
|
||||
#
|
||||
# Requests to static files are not counted towards DoS; they are listed in the
|
||||
# 'tx.static_extensions' setting, which you can change in this file (see
|
||||
# section "HTTP Policy Settings").
|
||||
#
|
||||
# For a detailed description, see rule file REQUEST-912-DOS-PROTECTION.conf.
|
||||
#
|
||||
# Uncomment this rule to use this feature:
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900700,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:'tx.dos_burst_time_slice=60',\
|
||||
# setvar:'tx.dos_counter_threshold=100',\
|
||||
# setvar:'tx.dos_block_timeout=600'"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Check UTF-8 encoding ]] ------------------------------------------------
|
||||
#
|
||||
# The CRS can optionally check request contents for invalid UTF-8 encoding.
|
||||
# We only want to apply this check if UTF-8 encoding is actually used by the
|
||||
# site; otherwise it will result in false positives.
|
||||
#
|
||||
# Uncomment this rule to use this feature:
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900950,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.crs_validate_utf8_encoding=1"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Blocking Based on IP Reputation ]] ------------------------------------
|
||||
#
|
||||
# Blocking based on reputation is permanent in the CRS. Unlike other rules,
|
||||
# which look at the individual request, the blocking of IPs is based on
|
||||
# a persistent record in the IP collection, which remains active for a
|
||||
# certain amount of time.
|
||||
#
|
||||
# There are two ways an individual client can become flagged for blocking:
|
||||
# - External information (RBL, GeoIP, etc.)
|
||||
# - Internal information (Core Rules)
|
||||
#
|
||||
# The record in the IP collection carries a flag, which tags requests from
|
||||
# individual clients with a flag named IP.reput_block_flag.
|
||||
# But the flag alone is not enough to have a client blocked. There is also
|
||||
# a global switch named tx.do_reput_block. This is off by default. If you set
|
||||
# it to 1 (=On), requests from clients with the IP.reput_block_flag will
|
||||
# be blocked for a certain duration.
|
||||
#
|
||||
# Variables
|
||||
# ip.reput_block_flag Blocking flag for the IP collection record
|
||||
# ip.reput_block_reason Reason (= rule message) that caused to blocking flag
|
||||
# tx.do_reput_block Switch deciding if we really block based on flag
|
||||
# tx.reput_block_duration Setting to define the duration of a block
|
||||
#
|
||||
# It may be important to know, that all the other core rules are skipped for
|
||||
# requests, when it is clear that they carry the blocking flag in question.
|
||||
#
|
||||
# Uncomment this rule to use this feature:
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900960,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.do_reput_block=1"
|
||||
#
|
||||
# Uncomment this rule to change the blocking time:
|
||||
# Default: 300 (5 minutes)
|
||||
#
|
||||
#SecAction \
|
||||
# "id:900970,\
|
||||
# phase:1,\
|
||||
# nolog,\
|
||||
# pass,\
|
||||
# t:none,\
|
||||
# setvar:tx.reput_block_duration=300"
|
||||
|
||||
|
||||
#
|
||||
# -- [[ Collection timeout ]] --------------------------------------------------
|
||||
#
|
||||
# Set the SecCollectionTimeout directive from the ModSecurity default (1 hour)
|
||||
# to a lower setting which is appropriate to most sites.
|
||||
# This increases performance by cleaning out stale collection (block) entries.
|
||||
#
|
||||
# This value should be greater than or equal to:
|
||||
# tx.reput_block_duration (see section "Blocking Based on IP Reputation") and
|
||||
# tx.dos_block_timeout (see section "Anti-Automation / DoS Protection").
|
||||
#
|
||||
# Ref: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#wiki-SecCollectionTimeout
|
||||
|
||||
# Please keep this directive uncommented.
|
||||
# Default: 600 (10 minutes)
|
||||
SecCollectionTimeout 600
|
||||
|
||||
|
||||
#
|
||||
# -- [[ End of setup ]] --------------------------------------------------------
|
||||
#
|
||||
# The CRS checks the tx.crs_setup_version variable to ensure that the setup
|
||||
# has been loaded. If you are not planning to use this setup template,
|
||||
# you must manually set the tx.crs_setup_version variable before including
|
||||
# the CRS rules/* files.
|
||||
#
|
||||
# The variable is a numerical representation of the CRS version number.
|
||||
# E.g., v3.0.0 is represented as 300.
|
||||
#
|
||||
SecAction \
|
||||
"id:900990,\
|
||||
phase:1,\
|
||||
pass,\
|
||||
t:none,\
|
||||
nolog,\
|
||||
setvar:tx.crs_setup_version=335"
|
|
@ -0,0 +1,73 @@
|
|||
function contains(tab, val)
|
||||
for index, value in ipairs(tab) do
|
||||
if value == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function inputHeader(request, response)
|
||||
|
||||
if (request:getMethod() == "GET" or request:getMethod() == "HEAD" or request:getMethod() == "OPTIONS" or request:getMethod() == "TRACE") then
|
||||
-- these requests are not sensitive (do not manipulate state) and are thus not checked
|
||||
return
|
||||
end
|
||||
|
||||
-- patterns sets allowed domains or {}
|
||||
domains = {}
|
||||
|
||||
host = request:getHeader("Host")
|
||||
|
||||
if (host == nil) then
|
||||
-- Internet-based HTTP/1.1 servers MUST respond with a 400 (Bad Request) status code to any HTTP/1.1 request message which lacks a Host header field.
|
||||
request:getTracer():notice("VA05", "Missing Host header")
|
||||
response:setHeader("Content-Type", "text/plain")
|
||||
response:setBody("400 Bad Request")
|
||||
response:send(400)
|
||||
return
|
||||
end
|
||||
|
||||
-- extract host name
|
||||
host = host:match('([^:]+)')
|
||||
|
||||
referer = request:getHeader("Referer")
|
||||
if (referer ~= nil) then
|
||||
referer = referer:match('^%w+://([^/:]+)')
|
||||
if (referer ~= host and not contains(domains, referer)) then
|
||||
if (referer ~= nil) then
|
||||
request:getTracer():notice("VA01", "HTTP Referer header " .. referer .. " does not match host " .. host)
|
||||
else
|
||||
request:getTracer():notice("VA01", "HTTP Referer header " .. request:getHeader("Referer") .. " does not match pattern '^[a-zA-Z0-9]+://([^/:]+)'")
|
||||
end
|
||||
response:setHeader("Content-Type", "text/plain")
|
||||
response:setBody("403 Denied")
|
||||
response:send(403)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
origin = request:getHeader("Origin")
|
||||
if (origin ~= nil) then
|
||||
origin = origin:match('^%w+://([^/:]+)')
|
||||
if (origin ~= host and not contains(domains, origin)) then
|
||||
if (origin ~= nil) then
|
||||
request:getTracer():notice("VA01", "HTTP Origin header " .. origin .. " does not match host " .. host)
|
||||
else
|
||||
request:getTracer():notice("VA01", "HTTP Origin header " .. request:getHeader("Origin") .. " does not match pattern '^[a-zA-Z0-9]+://([^/:]+)'")
|
||||
end
|
||||
response:setHeader("Content-Type", "text/plain")
|
||||
response:setBody("403 Denied")
|
||||
response:send(403)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if (origin == nil and referer == nil) then
|
||||
request:getTracer():info("VA05", "Referer or Origin header is required for sensitive requests")
|
||||
response:setHeader("Content-Type", "text/plain")
|
||||
response:setBody("403 Denied")
|
||||
response:send(403)
|
||||
return
|
||||
end
|
||||
end
|