Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
Harry Kodden committed Jul 17, 2024
2 parents ea2fa8a + 1ce6924 commit 10e9231
Show file tree
Hide file tree
Showing 14 changed files with 297 additions and 91 deletions.
2 changes: 2 additions & 0 deletions ansible/requirements.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
collections:
- community.docker
8 changes: 5 additions & 3 deletions ansible/roles/common/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
name: gnupg
update_cache: yes

- name: install jmespath
apt:
name: python3-jmespath
update_cache: yes

- name: set login banner
copy:
src: issue
Expand All @@ -35,6 +40,3 @@
owner: root
group: root
mode: 0644

- name: install required pip modules
shell: pip install jmespath
4 changes: 3 additions & 1 deletion ansible/roles/rclone/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
rclone_directory: "/opt/rclone"
rclone_directory: "/opt/rclone"

surf_research_vault_documentation: "https://servicedesk.surf.nl/wiki/x/yplsB"
36 changes: 0 additions & 36 deletions ansible/roles/rclone/files/index.php

This file was deleted.

1 change: 1 addition & 0 deletions ansible/roles/rclone/files/mount.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Remote-User $uid;

location / {
root /var/www/html;
Expand Down
10 changes: 5 additions & 5 deletions ansible/roles/rclone/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,16 @@
src: oidc.conf.j2
dest: "{{ rclone_directory }}/oidc.conf"

- name: copy index.php
template:
src: index.php.j2
dest: "{{ rclone_directory }}/index.php"

- name: copy mount.conf
copy:
src: files/mount.conf
dest: "{{ rclone_directory }}/mount.conf"

- name: copy index.php
copy:
src: files/index.php
dest: "{{ rclone_directory }}/index.php"

- name: Stop rclone
community.docker.docker_compose_v2:
project_src: "{{ rclone_directory }}"
Expand Down
47 changes: 47 additions & 0 deletions ansible/roles/rclone/templates/index.php.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
echo "<h1>Research Vault</h1>";

echo "<a href=\"{{ surf_research_vault_documentation }}\">Research Vault documentation</a><br/>";

echo "<h2>Research Vault server addresses</h2>";

$message = "No addresses configured yet. Administrator needs to do that first.";

echo "<ul>";
foreach (glob("/etc/mounts/*.conf") as $filename) {
$path = basename($filename, ".conf");

if ($path != "admin") {
$message = "";

$path = "webdav/" . $path;

echo "<li>'<a href=\"" . $_SERVER['url'] . "/" . $path . "\">" . $path . "'</a></li>";
}
}
echo "</ul>";

if ($message != "") {
echo $message;
} else {
echo "<h2>Authentication</h2>";
echo "Authenticate with your SRAM User ID and your SRAM Token that you have registered for this SRAM Service";
}

echo "<h2>Administration</h2>";

echo "You need to be member of the <b>{{ ADMIN_GROUP }}</b>";

echo "<ul>";
foreach (glob("/etc/mounts/*.conf") as $filename) {
$path = basename($filename, ".conf");
if ($path == "admin") {
$description = "Administration page";
echo "<li>rClone GUI <a href=\"" . $_SERVER['url'] . "/" . $path . "\">" . $description . "</a></li>";
echo "<li>rClone API <a href=\"" . $_SERVER['url'] . "/" . $path . "/api/doc\">" . $description . " (API Documentation)</a></li>";
}
}
echo "</ul>";

// phpinfo();
?>
3 changes: 3 additions & 0 deletions ansible/roles/vault/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
- name: Check Vault health...
uri:
url: "https://{{ inventory_hostname }}/v1/sys/health"
validate_certs: false
register: health

- name: Show health...
Expand All @@ -93,6 +94,7 @@
body: "{{ lookup('file','admin-policy.json') }}"
body_format: json
status_code: 204
validate_certs: false
headers:
Content-Type: application/json
X-Vault-Token: "{{ vault_token }}"
Expand All @@ -108,6 +110,7 @@
body: '{ "password": "{{ admin_password }}", "policies": "admin,default"}'
body_format: json
status_code: 204
validate_certs: false
headers:
Content-Type: application/json
X-Vault-Token: "{{ vault_token }}"
Expand Down
9 changes: 7 additions & 2 deletions ansible/vars/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@
admin_username: admin
log_level: ERROR

sram_urn_prefix: "urn:mace:surf.nl:sram:group"

ADMIN_GROUP: '{{ lookup("ansible.builtin.env", "ADMIN_GROUP", default="<undefined>") }}'
USERS_GROUP: '{{ lookup("ansible.builtin.env", "USERS_GROUP", default="*") }}'

SRAM_URL: '{{ lookup("ansible.builtin.env", "SRAM_URL", default="https://sram.surf.nl") }}'

SRAM_OIDC_BASE_URL: '{{ lookup("ansible.builtin.env", "SRAM_OIDC_BASE_URL", default="https://proxy.sram.surf.nl") }}'
SRAM_OIDC_CLIENT_ID: '{{ lookup("ansible.builtin.env", "SRAM_OIDC_CLIENT_ID", default="<undefined>") }}'
SRAM_OIDC_CLIENT_SECRET: '{{ lookup("ansible.builtin.env", "SRAM_OIDC_CLIENT_SECRET", default="<undefined>") }}'

SRAM_ADMIN_ACCESS_GROUP: '{{ lookup("ansible.builtin.env", "SRAM_ADMIN_ACCESS_GROUP", default="urn:mace:surf.nl:sram:group:...") }}'
SRAM_SERVICE_BEARER_TOKEN: '{{ lookup("ansible.builtin.env", "SRAM_SERVICE_BEARER_TOKEN", default="<undefined>") }}'

PROXY_ADMIN_PASSWORD: '{{ lookup("ansible.builtin.env", "PROXY_ADMIN_PASSWORD", default="admin") }}'

PAM_VALIDATE_USERS_ENTITLEMENT: "lambda mount: 'urn:mace:surf.nl:sram:group:{}'.format(mount.replace('-',':'))"
SRAM_ADMIN_ACCESS_GROUP: '{{ sram_urn_prefix }}:{{ ADMIN_GROUP }}'
PAM_VALIDATE_USERS_ENTITLEMENT: '{{ sram_urn_prefix }}:{{ USERS_GROUP }}'
112 changes: 105 additions & 7 deletions rclone/app/api/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,118 @@
import settings
import werkzeug
import uuid
import json

from flask import request, send_from_directory
from io import BytesIO
from flask import request, send_file, send_from_directory
from flask_restplus import Resource, fields, reqparse

from api import api, token_required, remote_user_required

from vault import rClone

log = logging.getLogger(__name__)
from api.config.crypto import encrypt, decrypt

log = logging.getLogger()

ns = api.namespace('config', description='Operations related to services')

my_rclone = rClone()

file_upload = reqparse.RequestParser()
file_upload.add_argument('rclone_config_file',
type=werkzeug.datastructures.FileStorage,
location='files',
required=True,
help='Config file')
file_upload.add_argument(
'rclone_config_file',
type=werkzeug.datastructures.FileStorage,
location='files',
required=True,
help='Config file'
)

crypted_data = reqparse.RequestParser()
crypted_data.add_argument(
'crypted_data',
type=str,
required=True,
help='enter crypted data'
)


@ns.route('/recover')
class Recover(Resource):

def get(self):
"""
Returns crypted configuration.
"""
result = {}

try:
me = request.headers['Remote-User'].replace('"','')

passphrase = my_rclone.passphrase(me, reset=False)['passphrase']

data = {}
for mount in my_rclone.dump().keys():
config = {
'name': mount,
'config': my_rclone.read_rclone_private_config(mount)
}

data[mount] = encrypt(
passphrase.encode(),
json.dumps(config).encode()
)

return data
except Exception as e:
return str(e), 400

return result

@api.expect(crypted_data)
def put(self):
"""
Returns decrypted data of given crypted input.
"""
try:
args = crypted_data.parse_args()

me = request.headers['Remote-User'].replace('"','')

passphrase = my_rclone.passphrase(me, reset=False)['passphrase']

data = json.loads(
decrypt(passphrase.encode(), args['crypted_data'])
)

return send_file(BytesIO(data['config'].encode()), attachment_filename=data['name']+'.conf', as_attachment=True )
except Exception as e:
return str(e), 401

@ns.route('/passphrase')
class PassPhrase(Resource):

def get(self):
"""
Get my passphrase from Vault
"""
try:
admin = request.headers['Remote-User'].replace('"', '')

return my_rclone.passphrase(admin, reset=False)
except:
return {}

def put(self):
"""
(Re-)Set my passphrase in Vault
"""
try:
admin = request.headers['Remote-User'].replace('"', '')

return my_rclone.passphrase(admin, reset=True)
except:
return {}


@ns.route('/dump')
Expand Down Expand Up @@ -91,6 +183,7 @@ def delete(self, mount):
'parameters': fields.Raw(required=True, description='Mount Parameters')
})


@ns.route('/get', methods=['POST'])
class get(Mount):

Expand All @@ -117,6 +210,7 @@ def post(self):

return super().post(mount, payload={ 'config' : config })


@ns.route('/update', methods=['POST'])
class update(Mount):

Expand All @@ -131,6 +225,7 @@ def post(self):

return super().post(mount, payload={ 'config' : config })


@ns.route('/delete', methods=['POST'])
class delete(Mount):

Expand All @@ -142,6 +237,7 @@ def post(self):

return self.delete(api.payload['name'])


@ns.route('/listremotes',methods=['POST'])
class ListRemotes(Config):

Expand All @@ -151,6 +247,7 @@ def post(self):
"""
return { "remotes": list(super().post().keys()) }


@ns.route('/export',methods=['GET'])
class Export(Config):

Expand All @@ -169,6 +266,7 @@ def get(self):
finally:
os.remove(dir+'/'+file)


@ns.route('/import', methods=['POST'])
class Import(ListRemotes):

Expand Down
25 changes: 25 additions & 0 deletions rclone/app/api/config/crypto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto import Random

def encrypt(key, source, encode=True):
key = SHA256.new(key).digest() # use SHA-256 over our key to get a proper-sized AES key
IV = Random.new().read(AES.block_size) # generate IV
encryptor = AES.new(key, AES.MODE_CBC, IV)
padding = AES.block_size - len(source) % AES.block_size # calculate needed padding
source += bytes([padding]) * padding # Python 2.x: source += chr(padding) * padding
data = IV + encryptor.encrypt(source) # store the IV at the beginning and encrypt
return base64.b64encode(data).decode() if encode else data

def decrypt(key, source, decode=True):
if decode:
source = base64.b64decode(source.encode())
key = SHA256.new(key).digest() # use SHA-256 over our key to get a proper-sized AES key
IV = source[:AES.block_size] # extract the IV from the beginning
decryptor = AES.new(key, AES.MODE_CBC, IV)
data = decryptor.decrypt(source[AES.block_size:]) # decrypt
padding = data[-1] # pick the padding value from the end; Python 2.x: ord(data[-1])
if data[-padding:] != bytes([padding]) * padding: # Python 2.x: chr(padding) * padding
raise ValueError("Invalid padding...")
return data[:-padding] # remove the padding
import base64
Loading

0 comments on commit 10e9231

Please sign in to comment.