Initial commit.
This commit is contained in:
commit
9f27e056d5
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
### Vim ###
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
Sessionx.vim
|
||||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
*~
|
||||
# Auto-generated tag files
|
||||
tags
|
||||
# Persistent undo
|
||||
[._]*.un~
|
||||
|
||||
### Rust ###
|
||||
**/target
|
||||
|
||||
### Nix $$$
|
||||
**/result
|
||||
78
authentik.nix
Normal file
78
authentik.nix
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
|
||||
environment = {
|
||||
AUTHENTIK_POSTGRESQL__HOST = "10.88.0.1";
|
||||
AUTHENTIK_REDIS__HOST = "10.88.0.1";
|
||||
};
|
||||
|
||||
in {
|
||||
networking.firewall.trustedInterfaces = [ "podman0" ];
|
||||
services.postgresql = {
|
||||
enableTCPIP = true;
|
||||
ensureDatabases = ["authentik"];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "authentik";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
authentication = ''
|
||||
host all all 10.88.0.0/16 md5
|
||||
'';
|
||||
};
|
||||
services.redis = {
|
||||
servers.authentik = {
|
||||
enable = true;
|
||||
requirePassFile = "/var/secrets/redis-password";
|
||||
port = 6379;
|
||||
bind = "10.88.0.1";
|
||||
};
|
||||
};
|
||||
virtualisation.podman.enable = true;
|
||||
virtualisation.oci-containers.backend = "podman";
|
||||
virtualisation.oci-containers.containers = {
|
||||
authentik-server = {
|
||||
image = "ghcr.io/goauthentik/server:2024.12.2";
|
||||
cmd = [ "server" ];
|
||||
inherit environment;
|
||||
environmentFiles = [ "/opt/authentik.env" ];
|
||||
ports = [ "10.88.0.1:9000:9000" ];
|
||||
volumes = [
|
||||
#"${dataDir}/media:/media"
|
||||
#"${dataDir}/assets:/web/dist/extra:ro"
|
||||
#"${dataDir}/templates:/templates"
|
||||
];
|
||||
};
|
||||
authentik-worker = {
|
||||
image = "ghcr.io/goauthentik/server:2024.12.2";
|
||||
cmd = [ "worker" ];
|
||||
inherit environment;
|
||||
environmentFiles = [ "/opt/authentik.env" ];
|
||||
volumes = [
|
||||
#"${dataDir}/media:/media"
|
||||
#"${dataDir}/assets:/web/dist/extra:ro"
|
||||
#"${dataDir}/templates:/templates"
|
||||
];
|
||||
};
|
||||
authentik-ldap = {
|
||||
image = "ghcr.io/goauthentik/ldap:2024.12.2";
|
||||
ports = [ "10.88.0.1:389:3389" "10.88.0.1:636:6636" ];
|
||||
environment = {
|
||||
AUTHENTIK_HOST = "https://auth.orga.cebula.camp";
|
||||
};
|
||||
environmentFiles = [
|
||||
"/var/secrets/authentik-ldap"
|
||||
];
|
||||
};
|
||||
};
|
||||
services.nginx.virtualHosts."auth.orga.cebula.camp" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://10.88.0.1:9000/";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
191
configuration.nix
Normal file
191
configuration.nix
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
# Edit this configuration file to define what should be installed on
|
||||
# your system. Help is available in the configuration.nix(5) man page
|
||||
# and in the NixOS manual (accessible by running ‘nixos-help’).
|
||||
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ # Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
#./identity.nix
|
||||
./mailserver.nix
|
||||
./nextcloud.nix
|
||||
./authentik.nix
|
||||
./forgejo.nix
|
||||
];
|
||||
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
|
||||
networking.hostName = "szalotka";
|
||||
networking.domain = "cebula.camp";
|
||||
|
||||
networking.useDHCP = false;
|
||||
networking.interfaces.enp1s0 = {
|
||||
ipv4.addresses = [
|
||||
{ address = "135.181.235.222"; prefixLength = 29; }
|
||||
];
|
||||
ipv6.addresses = [
|
||||
{ address = "2a01:4f9:4a:4319:1337::14"; prefixLength = 80; }
|
||||
];
|
||||
};
|
||||
networking.defaultGateway = "135.181.235.217";
|
||||
networking.defaultGateway6 = "2a01:4f9:4a:4319:1337::1";
|
||||
networking.nameservers = [
|
||||
"8.8.8.8"
|
||||
];
|
||||
|
||||
# Define a user account. Don't forget to set a password with ‘passwd’.
|
||||
# users.users.jane = {
|
||||
# isNormalUser = true;
|
||||
# extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
|
||||
# };
|
||||
|
||||
users.users.root = {
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG599UildOrAq+LIOQjKqtGMwjgjIxozI1jtQQRKHtCP q3k@mimeomia"
|
||||
"cert-authority ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPt2EXhvAwjMZ+5j8P0dCMaUdXeUQePeTv8tBdHXNly mewp"
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQb3YQoiYFZLKwvHYKbu1bMqzNeDCAszQhAe1+QI5SLDOotclyY/vFmOReZOsmyMFl71G2d7d+FbYNusUnNNjTxRYQ021tVc+RkMdLJaORRURmQfEFEKbai6QSFTwErXzuoIzyEPK0lbsQuGgqT9WaVnRzHJ2Q/4+qQbxAS34PuR5NqEkmn4G6LMo3OyJ5mwPkCj9lsqz4BcxRaMWFO3mNcwGDfSW+sqgc3E8N6LKrTpZq3ke7xacpQmcG5DU9VO+2QVPdltl9jWbs3gXjmF92YRNOuKPVfAOZBBsp8JOznfx8s9wDgs7RwPmDpjIAJEyoABqW5hlXfqRbTnfnMvuR informatic@InformaticPC"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOccFr7RFddSB5kdvYCIdCyKgD3X79mC90cMImqziTd9 radex@radpro"
|
||||
];
|
||||
};
|
||||
users.groups.ldap-access = {};
|
||||
|
||||
# users.ldap = {
|
||||
# enable = true;
|
||||
# loginPam = true;
|
||||
# nsswitch = true;
|
||||
# base = "ou=users,dc=cebula,dc=camp";
|
||||
# server = "ldap://10.88.0.1:389/";
|
||||
# timeLimit = 1;
|
||||
# daemon.enable = true;
|
||||
# bind = {
|
||||
# distinguishedName = "cn=ldap-access,ou=users,dc=cebula,dc=camp";
|
||||
# passwordFile = "/var/secrets/ldap-access";
|
||||
# };
|
||||
# };
|
||||
|
||||
services.sssd = {
|
||||
enable = true;
|
||||
sshAuthorizedKeysIntegration = true;
|
||||
config = let
|
||||
baseDN = "dc=cebula,dc=camp";
|
||||
serviceAccount = "ldap-access";
|
||||
allowedGroup = "cn=orga-infra,ou=groups,${baseDN}";
|
||||
in ''
|
||||
[nss]
|
||||
filter_groups = root
|
||||
filter_users = root
|
||||
reconnection_retries = 3
|
||||
|
||||
[sssd]
|
||||
config_file_version = 2
|
||||
reconnection_retries = 3
|
||||
domains = LDAP
|
||||
services = nss, pam, ssh
|
||||
|
||||
[pam]
|
||||
reconnection_retries = 3
|
||||
|
||||
[domain/LDAP]
|
||||
cache_credentials = True
|
||||
id_provider = ldap
|
||||
chpass_provider = ldap
|
||||
auth_provider = ldap
|
||||
access_provider = ldap
|
||||
|
||||
ldap_uri = ldaps://10.88.0.1/
|
||||
ldap_tls_reqcert = allow
|
||||
|
||||
ldap_schema = rfc2307bis
|
||||
ldap_search_base = ${baseDN}
|
||||
ldap_user_search_base = ou=users,${baseDN}
|
||||
ldap_group_search_base = ${baseDN}
|
||||
ldap_user_ssh_public_key = sshPublicKey
|
||||
|
||||
ldap_user_object_class = user
|
||||
ldap_user_name = cn
|
||||
ldap_group_object_class = group
|
||||
ldap_group_name = cn
|
||||
|
||||
ldap_default_bind_dn = cn=${serviceAccount},ou=users,${baseDN}
|
||||
ldap_default_authtok = $LDAP_DEFAULT_AUTHTOK
|
||||
ldap_access_filter = memberOf=${allowedGroup}
|
||||
'';
|
||||
environmentFile = "/var/secrets/sssd";
|
||||
};
|
||||
security.pam.services.sshd.makeHomeDir = true;
|
||||
security.sudo.extraRules = [{
|
||||
groups = ["orga-infra"];
|
||||
commands = [{
|
||||
command = "ALL";
|
||||
options = [ "NOPASSWD" ];
|
||||
}];
|
||||
}];
|
||||
|
||||
environment.variables.EDITOR = "vim";
|
||||
environment.systemPackages = with pkgs; [
|
||||
wget rxvt-unicode-unwrapped.terminfo htop dstat
|
||||
git
|
||||
|
||||
((vim_configurable.override { features = "normal"; }).customize {
|
||||
name = "vim";
|
||||
vimrcConfig.packages.myVimPackage = with pkgs.vimPlugins; {
|
||||
start = [ vim-nix nerdtree ];
|
||||
};
|
||||
vimrcConfig.customRC = ''
|
||||
syntax on
|
||||
set expandtab
|
||||
set tabstop=4
|
||||
set autoindent
|
||||
set shiftwidth=4
|
||||
set bs=2
|
||||
|
||||
autocmd FileType nix setlocal shiftwidth=2 tabstop=2
|
||||
'';
|
||||
})
|
||||
];
|
||||
|
||||
programs.mtr.enable = true;
|
||||
|
||||
services.journald.extraConfig = ''
|
||||
SystemMaxUse=2G
|
||||
'';
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
ports = [ 2222 ];
|
||||
};
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
recommendedProxySettings = true;
|
||||
};
|
||||
security.acme = {
|
||||
# TODO(q3k): change to @cebula.camp address.
|
||||
email = "q3k@q3k.org";
|
||||
acceptTerms = true;
|
||||
};
|
||||
# Limit nscd memory usage, as it sometimes just blows up and the OOMkiller
|
||||
# sucks at picking it up.
|
||||
systemd.services.nscd.serviceConfig.MemoryMax = "256M";
|
||||
|
||||
|
||||
# Open ports in the firewall.
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80 443 # http
|
||||
2222 # host ssh
|
||||
];
|
||||
# networking.firewall.allowedUDPPorts = [ ... ];
|
||||
# Or disable the firewall altogether.
|
||||
# networking.firewall.enable = false;
|
||||
|
||||
# This value determines the NixOS release from which the default
|
||||
# settings for stateful data, like file locations and database versions
|
||||
# on your system were taken. It‘s perfectly fine and recommended to leave
|
||||
# this value at the release version of the first install of this system.
|
||||
# Before changing this value read the documentation for this option
|
||||
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
|
||||
system.stateVersion = "20.09"; # Did you read the comment?
|
||||
|
||||
}
|
||||
|
||||
16
encryptor/.gitignore
vendored
Normal file
16
encryptor/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/target
|
||||
|
||||
|
||||
# Added by cargo
|
||||
#
|
||||
# already existing elements were commented out
|
||||
|
||||
#/target
|
||||
|
||||
|
||||
# Added by cargo
|
||||
#
|
||||
# already existing elements were commented out
|
||||
|
||||
#/target
|
||||
Cargo.lock
|
||||
6
encryptor/Cargo.toml
Normal file
6
encryptor/Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"encryptor",
|
||||
"decryptor",
|
||||
"common",
|
||||
]
|
||||
12
encryptor/common/Cargo.toml
Normal file
12
encryptor/common/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "common"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
protobuf = "2"
|
||||
secretbox = "0"
|
||||
base64 = "0"
|
||||
|
||||
[build-dependencies]
|
||||
protobuf-codegen-pure = "2.3"
|
||||
12
encryptor/common/build.rs
Normal file
12
encryptor/common/build.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
extern crate protobuf_codegen_pure;
|
||||
|
||||
fn main() {
|
||||
protobuf_codegen_pure::run(protobuf_codegen_pure::Args {
|
||||
out_dir: "src/protos",
|
||||
input: &["proto/smtp.proto"],
|
||||
includes: &["proto"],
|
||||
customize: protobuf_codegen_pure::Customize {
|
||||
..Default::default()
|
||||
},
|
||||
}).expect("protoc");
|
||||
}
|
||||
10
encryptor/common/proto/smtp.proto
Normal file
10
encryptor/common/proto/smtp.proto
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
syntax = "proto3";
|
||||
package camp.cebula.smtp;
|
||||
|
||||
message SMTPSource {
|
||||
message V1 {
|
||||
int64 timestamp = 1;
|
||||
string sender = 2;
|
||||
};
|
||||
bytes v1_encrypted = 1;
|
||||
}
|
||||
5
encryptor/common/src/lib.rs
Normal file
5
encryptor/common/src/lib.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub mod protos;
|
||||
|
||||
pub const KEY: &'static str = "thiYux7lohfit2queibee9engo1eiVoi";
|
||||
pub const B64C: base64::Config = base64::Config::new(base64::CharacterSet::UrlSafe, false);
|
||||
|
||||
1
encryptor/common/src/protos/mod.rs
Normal file
1
encryptor/common/src/protos/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod smtp;
|
||||
396
encryptor/common/src/protos/smtp.rs
Normal file
396
encryptor/common/src/protos/smtp.rs
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
// This file is generated by rust-protobuf 2.25.1. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::all)]
|
||||
|
||||
#![allow(unused_attributes)]
|
||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
|
||||
#![allow(box_pointers)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(trivial_casts)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_results)]
|
||||
//! Generated file from `smtp.proto`
|
||||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_1;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct SMTPSource {
|
||||
// message fields
|
||||
pub v1_encrypted: ::std::vec::Vec<u8>,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a SMTPSource {
|
||||
fn default() -> &'a SMTPSource {
|
||||
<SMTPSource as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl SMTPSource {
|
||||
pub fn new() -> SMTPSource {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
// bytes v1_encrypted = 1;
|
||||
|
||||
|
||||
pub fn get_v1_encrypted(&self) -> &[u8] {
|
||||
&self.v1_encrypted
|
||||
}
|
||||
pub fn clear_v1_encrypted(&mut self) {
|
||||
self.v1_encrypted.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_v1_encrypted(&mut self, v: ::std::vec::Vec<u8>) {
|
||||
self.v1_encrypted = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_v1_encrypted(&mut self) -> &mut ::std::vec::Vec<u8> {
|
||||
&mut self.v1_encrypted
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_v1_encrypted(&mut self) -> ::std::vec::Vec<u8> {
|
||||
::std::mem::replace(&mut self.v1_encrypted, ::std::vec::Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for SMTPSource {
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
while !is.eof()? {
|
||||
let (field_number, wire_type) = is.read_tag_unpack()?;
|
||||
match field_number {
|
||||
1 => {
|
||||
::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.v1_encrypted)?;
|
||||
},
|
||||
_ => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u32 {
|
||||
let mut my_size = 0;
|
||||
if !self.v1_encrypted.is_empty() {
|
||||
my_size += ::protobuf::rt::bytes_size(1, &self.v1_encrypted);
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||
self.cached_size.set(my_size);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
if !self.v1_encrypted.is_empty() {
|
||||
os.write_bytes(1, &self.v1_encrypted)?;
|
||||
}
|
||||
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn get_cached_size(&self) -> u32 {
|
||||
self.cached_size.get()
|
||||
}
|
||||
|
||||
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
|
||||
&self.unknown_fields
|
||||
}
|
||||
|
||||
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
|
||||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
Self::descriptor_static()
|
||||
}
|
||||
|
||||
fn new() -> SMTPSource {
|
||||
SMTPSource::new()
|
||||
}
|
||||
|
||||
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||
descriptor.get(|| {
|
||||
let mut fields = ::std::vec::Vec::new();
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
|
||||
"v1_encrypted",
|
||||
|m: &SMTPSource| { &m.v1_encrypted },
|
||||
|m: &mut SMTPSource| { &mut m.v1_encrypted },
|
||||
));
|
||||
::protobuf::reflect::MessageDescriptor::new_pb_name::<SMTPSource>(
|
||||
"SMTPSource",
|
||||
fields,
|
||||
file_descriptor_proto()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static SMTPSource {
|
||||
static instance: ::protobuf::rt::LazyV2<SMTPSource> = ::protobuf::rt::LazyV2::INIT;
|
||||
instance.get(SMTPSource::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Clear for SMTPSource {
|
||||
fn clear(&mut self) {
|
||||
self.v1_encrypted.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for SMTPSource {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for SMTPSource {
|
||||
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
|
||||
::protobuf::reflect::ReflectValueRef::Message(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct SMTPSource_V1 {
|
||||
// message fields
|
||||
pub timestamp: i64,
|
||||
pub sender: ::std::string::String,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a SMTPSource_V1 {
|
||||
fn default() -> &'a SMTPSource_V1 {
|
||||
<SMTPSource_V1 as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl SMTPSource_V1 {
|
||||
pub fn new() -> SMTPSource_V1 {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
// int64 timestamp = 1;
|
||||
|
||||
|
||||
pub fn get_timestamp(&self) -> i64 {
|
||||
self.timestamp
|
||||
}
|
||||
pub fn clear_timestamp(&mut self) {
|
||||
self.timestamp = 0;
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_timestamp(&mut self, v: i64) {
|
||||
self.timestamp = v;
|
||||
}
|
||||
|
||||
// string sender = 2;
|
||||
|
||||
|
||||
pub fn get_sender(&self) -> &str {
|
||||
&self.sender
|
||||
}
|
||||
pub fn clear_sender(&mut self) {
|
||||
self.sender.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_sender(&mut self, v: ::std::string::String) {
|
||||
self.sender = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_sender(&mut self) -> &mut ::std::string::String {
|
||||
&mut self.sender
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_sender(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.sender, ::std::string::String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for SMTPSource_V1 {
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
while !is.eof()? {
|
||||
let (field_number, wire_type) = is.read_tag_unpack()?;
|
||||
match field_number {
|
||||
1 => {
|
||||
if wire_type != ::protobuf::wire_format::WireTypeVarint {
|
||||
return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
|
||||
}
|
||||
let tmp = is.read_int64()?;
|
||||
self.timestamp = tmp;
|
||||
},
|
||||
2 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.sender)?;
|
||||
},
|
||||
_ => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u32 {
|
||||
let mut my_size = 0;
|
||||
if self.timestamp != 0 {
|
||||
my_size += ::protobuf::rt::value_size(1, self.timestamp, ::protobuf::wire_format::WireTypeVarint);
|
||||
}
|
||||
if !self.sender.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(2, &self.sender);
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||
self.cached_size.set(my_size);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
if self.timestamp != 0 {
|
||||
os.write_int64(1, self.timestamp)?;
|
||||
}
|
||||
if !self.sender.is_empty() {
|
||||
os.write_string(2, &self.sender)?;
|
||||
}
|
||||
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn get_cached_size(&self) -> u32 {
|
||||
self.cached_size.get()
|
||||
}
|
||||
|
||||
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
|
||||
&self.unknown_fields
|
||||
}
|
||||
|
||||
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
|
||||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
Self::descriptor_static()
|
||||
}
|
||||
|
||||
fn new() -> SMTPSource_V1 {
|
||||
SMTPSource_V1::new()
|
||||
}
|
||||
|
||||
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||
descriptor.get(|| {
|
||||
let mut fields = ::std::vec::Vec::new();
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
|
||||
"timestamp",
|
||||
|m: &SMTPSource_V1| { &m.timestamp },
|
||||
|m: &mut SMTPSource_V1| { &mut m.timestamp },
|
||||
));
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
|
||||
"sender",
|
||||
|m: &SMTPSource_V1| { &m.sender },
|
||||
|m: &mut SMTPSource_V1| { &mut m.sender },
|
||||
));
|
||||
::protobuf::reflect::MessageDescriptor::new_pb_name::<SMTPSource_V1>(
|
||||
"SMTPSource.V1",
|
||||
fields,
|
||||
file_descriptor_proto()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static SMTPSource_V1 {
|
||||
static instance: ::protobuf::rt::LazyV2<SMTPSource_V1> = ::protobuf::rt::LazyV2::INIT;
|
||||
instance.get(SMTPSource_V1::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Clear for SMTPSource_V1 {
|
||||
fn clear(&mut self) {
|
||||
self.timestamp = 0;
|
||||
self.sender.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for SMTPSource_V1 {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for SMTPSource_V1 {
|
||||
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
|
||||
::protobuf::reflect::ReflectValueRef::Message(self)
|
||||
}
|
||||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\nsmtp.proto\x12\x10camp.cebula.smtp\"u\n\nSMTPSource\x12#\n\x0cv1_enc\
|
||||
rypted\x18\x01\x20\x01(\x0cR\x0bv1EncryptedB\0\x1a@\n\x02V1\x12\x1e\n\tt\
|
||||
imestamp\x18\x01\x20\x01(\x03R\ttimestampB\0\x12\x18\n\x06sender\x18\x02\
|
||||
\x20\x01(\tR\x06senderB\0:\0:\0B\0b\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
||||
fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
|
||||
::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
|
||||
}
|
||||
|
||||
pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
|
||||
file_descriptor_proto_lazy.get(|| {
|
||||
parse_descriptor_proto()
|
||||
})
|
||||
}
|
||||
10
encryptor/decryptor/Cargo.toml
Normal file
10
encryptor/decryptor/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "decryptor"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
common = { path = "../common" }
|
||||
protobuf = "2"
|
||||
secretbox = "0"
|
||||
base64 = "0"
|
||||
23
encryptor/decryptor/src/main.rs
Normal file
23
encryptor/decryptor/src/main.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use protobuf::Message;
|
||||
use std::io::Read;
|
||||
|
||||
use common::{protos,B64C,KEY};
|
||||
|
||||
fn main() {
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_to_string(&mut input).unwrap();
|
||||
let input: String = input.split("\n").collect::<Vec<&str>>().join("").chars().filter(|c| !c.is_whitespace()).collect();
|
||||
|
||||
let s_bytes = base64::decode_config(input, B64C).expect("base64 decode");
|
||||
let s = protos::smtp::SMTPSource::parse_from_bytes(&s_bytes).expect("deproto");
|
||||
let v1_encrypted = s.get_v1_encrypted();
|
||||
if v1_encrypted.len() == 0 {
|
||||
println!("v1_encrypted not set");
|
||||
return;
|
||||
}
|
||||
let b = secretbox::SecretBox::new(KEY, secretbox::CipherType::Salsa20).unwrap();
|
||||
let v1_bytes = b.easy_unseal(&v1_encrypted).expect("invalid encrypted data");
|
||||
|
||||
let v1 = protos::smtp::SMTPSource_V1::parse_from_bytes(&v1_bytes).expect("deproto v1");
|
||||
println!("{:?}", v1);
|
||||
}
|
||||
14
encryptor/encryptor/Cargo.toml
Normal file
14
encryptor/encryptor/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "encryptor"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "encryptor"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
common = { path = "../common" }
|
||||
protobuf = "2"
|
||||
secretbox = "0"
|
||||
base64 = "0"
|
||||
64
encryptor/encryptor/src/lib.rs
Normal file
64
encryptor/encryptor/src/lib.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use std::os::raw;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use protobuf::Message;
|
||||
|
||||
use common::{protos,B64C,KEY};
|
||||
|
||||
extern "C" {
|
||||
fn string_copyn_function(
|
||||
i: *const raw::c_char,
|
||||
l: raw::c_int,
|
||||
) -> *mut raw::c_char;
|
||||
}
|
||||
|
||||
fn write_result(yield_: *mut*const raw::c_char, s: String) {
|
||||
let s: &str = s.as_str();
|
||||
let ptr = s.as_ptr() as *const raw::c_char;
|
||||
unsafe {
|
||||
let copy = string_copyn_function(ptr, s.len() as i32);
|
||||
*yield_ = copy;
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn encryptor(yield_: *mut*const raw::c_char, argc: raw::c_int, argv: *const*const raw::c_char) -> raw::c_int {
|
||||
if argc != 1 {
|
||||
write_result(yield_, format!("wrong arg count (want one, got {})", argc));
|
||||
return 3;
|
||||
}
|
||||
|
||||
let cstr = unsafe { std::ffi::CStr::from_ptr(*argv) };
|
||||
let sender = match cstr.to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
write_result(yield_, "invalid unicode".to_string());
|
||||
return 3;
|
||||
}
|
||||
};
|
||||
|
||||
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as i64;
|
||||
|
||||
let mut v1 = protos::smtp::SMTPSource_V1::new();
|
||||
v1.set_timestamp(now);
|
||||
v1.set_sender(sender.to_string());
|
||||
let v1_bytes = v1.write_to_bytes().unwrap();
|
||||
|
||||
let b = secretbox::SecretBox::new(KEY, secretbox::CipherType::Salsa20).unwrap();
|
||||
let v1_encrypted = b.easy_seal(&v1_bytes);
|
||||
|
||||
let mut s = protos::smtp::SMTPSource::new();
|
||||
s.set_v1_encrypted(v1_encrypted);
|
||||
let s_bytes = s.write_to_bytes().unwrap();
|
||||
|
||||
let s_base64 = base64::encode_config(s_bytes, B64C);
|
||||
let wrapped = s_base64.chars()
|
||||
.collect::<Vec<char>>()
|
||||
.chunks(70)
|
||||
.map(|c| c.iter().collect::<String>())
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n ");
|
||||
|
||||
write_result(yield_, wrapped);
|
||||
return 0;
|
||||
}
|
||||
67
forgejo.nix
Normal file
67
forgejo.nix
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
nixpkgs.overlays = [
|
||||
(self: super: {
|
||||
# Always add '@cebula.camp' to LDAP email attribute, so that we can set
|
||||
# the attribute to 'cn'. We don't have any other way to get an
|
||||
# @cebula.camp email address.
|
||||
forgejo-lts = super.forgejo-lts.overrideAttrs (oa: {
|
||||
patches = oa.patches ++ [
|
||||
./forgejo/0001-bad-bad-not-good-patch-it-s-3-am-and-i-am-tired.patch
|
||||
];
|
||||
doCheck = false;
|
||||
});
|
||||
})
|
||||
];
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
lfs.enable = true;
|
||||
settings = {
|
||||
service = {
|
||||
DISABLE_REGISTRATION = false;
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
|
||||
ENABLE_NOTIFY_MAIL = false;
|
||||
};
|
||||
server = {
|
||||
ROOT_URL = "https://git.orga.cebula.camp";
|
||||
HTTP_PORT = 3001;
|
||||
DOMAIN = "git.orga.cebula.camp";
|
||||
START_SSH_SERVER = true;
|
||||
SSH_PORT = 22;
|
||||
SSH_LISTEN_PORT = 2223;
|
||||
BUILTIN_SSH_SERVER_USER = "git";
|
||||
};
|
||||
oauth2_client = {
|
||||
REGISTER_EMAIL_CONFIRM = false;
|
||||
ENABLE_AUTO_REGISTRATION = true;
|
||||
USERNAME = "nickname";
|
||||
ACCOUNT_LINKING = "auto";
|
||||
};
|
||||
DEFAULT = {
|
||||
APP_ANME = "CebulaGit";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
#systemd.services.forgejo-secrets.script = ''
|
||||
# ${pkgs.forgejo}/bin/gitea admin user create --username bofh --password dupa.8 --email q3k@q3k.org --admin --must-change-password=false
|
||||
#'';
|
||||
|
||||
services.nginx.virtualHosts."git.orga.cebula.camp" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://localhost:3001";
|
||||
};
|
||||
};
|
||||
|
||||
# redirect external port 22 to internal 2223
|
||||
networking.firewall.allowedTCPPorts = [ 22 2223 ];
|
||||
networking.firewall.extraCommands = ''
|
||||
iptables -t nat -A PREROUTING -p tcp --dport 22 -j REDIRECT --to-port 2223
|
||||
'';
|
||||
networking.firewall.extraStopCommands = ''
|
||||
iptables -t nat -F PREROUTING
|
||||
'';
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
From 09949e09b153d14bffdd765d48394f046a0848ef Mon Sep 17 00:00:00 2001
|
||||
From: Serge Bazanski <q3k@q3k.org>
|
||||
Date: Mon, 20 Jan 2025 02:58:07 +0100
|
||||
Subject: [PATCH] bad bad not good patch it's 3 am and i am tired
|
||||
|
||||
---
|
||||
services/auth/source/ldap/source_search.go | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go
|
||||
index 2a61386ae1..aceefe53ad 100644
|
||||
--- a/services/auth/source/ldap/source_search.go
|
||||
+++ b/services/auth/source/ldap/source_search.go
|
||||
@@ -362,7 +362,7 @@ func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchR
|
||||
username := sr.Entries[0].GetAttributeValue(source.AttributeUsername)
|
||||
firstname := sr.Entries[0].GetAttributeValue(source.AttributeName)
|
||||
surname := sr.Entries[0].GetAttributeValue(source.AttributeSurname)
|
||||
- mail := sr.Entries[0].GetAttributeValue(source.AttributeMail)
|
||||
+ mail := sr.Entries[0].GetAttributeValue(source.AttributeMail) + "@cebula.camp"
|
||||
|
||||
if isAttributeSSHPublicKeySet {
|
||||
sshPublicKey = sr.Entries[0].GetAttributeValues(source.AttributeSSHPublicKey)
|
||||
@@ -490,7 +490,7 @@ func (source *Source) SearchEntries() ([]*SearchResult, error) {
|
||||
Username: v.GetAttributeValue(source.AttributeUsername),
|
||||
Name: v.GetAttributeValue(source.AttributeName),
|
||||
Surname: v.GetAttributeValue(source.AttributeSurname),
|
||||
- Mail: v.GetAttributeValue(source.AttributeMail),
|
||||
+ Mail: v.GetAttributeValue(source.AttributeMail) + "@cebula.camp",
|
||||
IsAdmin: checkAdmin(l, source, v.DN),
|
||||
Groups: usersLdapGroups,
|
||||
}
|
||||
--
|
||||
2.44.2
|
||||
|
||||
72
forgejo/signin_inner.tmpl
Normal file
72
forgejo/signin_inner.tmpl
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn)}}
|
||||
{{template "base/alert" .}}
|
||||
{{end}}
|
||||
<h4 class="ui top attached header center">
|
||||
{{if .LinkAccountMode}}
|
||||
{{ctx.Locale.Tr "auth.oauth_signin_title"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "auth.login_userpass"}}
|
||||
{{end}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form tw-max-w-2xl tw-m-auto" action="{{.SignInLink}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="tw-hidden">
|
||||
<div class="required field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||
<label for="user_name">{{ctx.Locale.Tr "home.uname_holder"}}</label>
|
||||
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required>
|
||||
</div>
|
||||
{{if or (not .DisablePassword) .LinkAccountMode}}
|
||||
<div class="required field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||
<label for="password">{{ctx.Locale.Tr "password"}}</label>
|
||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="current-password" required>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .LinkAccountMode}}
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox">
|
||||
<label>{{ctx.Locale.Tr "auth.remember_me"}}</label>
|
||||
<input name="remember" type="checkbox">
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{template "user/auth/captcha" .}}
|
||||
|
||||
<div class="field">
|
||||
<button class="ui primary button">
|
||||
{{if .LinkAccountMode}}
|
||||
{{ctx.Locale.Tr "auth.oauth_signin_submit"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "sign_in"}}
|
||||
{{end}}
|
||||
</button>
|
||||
<a href="{{AppSubUrl}}/user/forgot_password">{{ctx.Locale.Tr "auth.forgot_password"}}</a>
|
||||
</div>
|
||||
|
||||
{{if .ShowRegistrationButton}}
|
||||
<div class="field">
|
||||
<a href="{{AppSubUrl}}/user/sign_up">{{ctx.Locale.Tr "auth.sign_up_now"}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .OAuth2Providers}}
|
||||
<div class="divider divider-text">
|
||||
{{ctx.Locale.Tr "sign_in_or"}}
|
||||
</div>
|
||||
</div>
|
||||
<div id="oauth2-login-navigator" class="tw-py-1">
|
||||
<div class="tw-flex tw-flex-col tw-justify-center">
|
||||
<div id="oauth2-login-navigator-inner" class="tw-flex tw-flex-col tw-flex-wrap tw-items-center tw-gap-2">
|
||||
{{range $provider := .OAuth2Providers}}
|
||||
<a class="{{$provider.Name}} ui button tw-flex tw-items-center tw-justify-center tw-py-2 tw-w-full oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}">
|
||||
{{$provider.IconHTML 28}}
|
||||
{{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</form>
|
||||
</div>
|
||||
28
hardware-configuration.nix
Normal file
28
hardware-configuration.nix
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||
# and may be overwritten by future invocations. Please make changes
|
||||
# to /etc/nixos/configuration.nix instead.
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ (modulesPath + "/profiles/qemu-guest.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ahci" "virtio_pci" "sr_mod" "virtio_blk" ];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" =
|
||||
{ device = "/dev/disk/by-uuid/32cb35b8-5b9e-41f7-888e-8b29be3e4c5e";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot" =
|
||||
{ device = "/dev/disk/by-uuid/A74D-903F";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
swapDevices = [ ];
|
||||
|
||||
}
|
||||
247
mailserver.nix
Normal file
247
mailserver.nix
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
{ config, pkgs, lib, ... }: let
|
||||
|
||||
aliases = lib.fix (self: {
|
||||
postmaster = [ "q3k" ];
|
||||
MAILER-DAEMON = self.postmaster;
|
||||
|
||||
security = self.postmaster;
|
||||
nobody = self.postmaster;
|
||||
usenet = self.postmaster;
|
||||
uucp = self.postmaster;
|
||||
webmaster = self.postmaster;
|
||||
www = self.postmaster;
|
||||
|
||||
orga = [ "rheya" "mewp" "martyna" ];
|
||||
cfp = [ "martyna" ];
|
||||
conduct = [ "mewp" "q3k" ];
|
||||
});
|
||||
mkAlias = name: members: "${name}: ${lib.concatStringsSep ", " members}";
|
||||
aliasFile = pkgs.writeTextFile {
|
||||
name = "aliases";
|
||||
text = lib.concatStringsSep "\n" (lib.mapAttrsToList mkAlias aliases);
|
||||
};
|
||||
acmeDir = config.security.acme.certs."mail.orga.cebula.camp".directory;
|
||||
|
||||
# Little tool to encrypt the SMTP source.
|
||||
encryptor = pkgs.rustPlatform.buildRustPackage {
|
||||
name = "encryptor";
|
||||
src = ./encryptor;
|
||||
#cargoSha256 = lib.fakeSha256;
|
||||
cargoSha256 = "sha256:1ky42jssq3sk2a3lf29qc019bwj73rczcaqdjaick3nmz7yf1679";
|
||||
};
|
||||
|
||||
in {
|
||||
security.acme.certs."mail.orga.cebula.camp" = {
|
||||
group = "acme-mail";
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 25 587 143 993 465 ];
|
||||
users.groups.acme-mail = {
|
||||
members = [
|
||||
config.services.nginx.user
|
||||
config.services.dovecot2.user
|
||||
config.services.exim.user
|
||||
];
|
||||
};
|
||||
services.nginx.virtualHosts."mail.orga.cebula.camp" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
services.exim = {
|
||||
enable = true;
|
||||
package = (pkgs.exim.override { enableLDAP = true; }).overrideAttrs (oa: {
|
||||
preBuild = ''
|
||||
sed '
|
||||
s:^# \(EXPAND_DLFUNC\)=.*:\1=yes:
|
||||
s:^# \(EXTRALIBS_EXIM\)=.*:\1=-ldl -rdynamic -export-dynamic:
|
||||
' -i Local/Makefile
|
||||
'';
|
||||
});
|
||||
config = ''
|
||||
primary_hostname = szalotka.cebula.camp
|
||||
domainlist local_domains = cebula.camp
|
||||
hostlist relay_from_hosts = 127.0.0.1
|
||||
qualify_domain = cebula.camp
|
||||
|
||||
acl_smtp_rcpt = acl_check_rcpt
|
||||
acl_smtp_data = acl_check_data
|
||||
|
||||
tls_advertise_hosts = *
|
||||
tls_certificate = ${acmeDir}/fullchain.pem
|
||||
tls_privatekey = ${acmeDir}/key.pem
|
||||
tls_on_connect_ports = 465
|
||||
|
||||
daemon_smtp_ports = 25 : 587 : 465
|
||||
|
||||
never_users = root
|
||||
host_lookup = *
|
||||
|
||||
rfc1413_hosts = *
|
||||
rfc1413_query_timeout = 5s
|
||||
|
||||
ignore_bounce_errors_after = 2d
|
||||
timeout_frozen_after = 7d
|
||||
|
||||
begin acl
|
||||
|
||||
acl_check_rcpt:
|
||||
accept hosts = :
|
||||
control = dkim_disable_verify
|
||||
|
||||
deny message = Restricted characters in address
|
||||
domains = +local_domains
|
||||
local_parts = ^[.] : ^.*[@%!/|]
|
||||
|
||||
accept local_parts = postmaster
|
||||
domains = +local_domains
|
||||
|
||||
require verify = sender
|
||||
|
||||
accept hosts = +relay_from_hosts
|
||||
control = submission
|
||||
control = dkim_disable_verify
|
||||
|
||||
accept authenticated = *
|
||||
control = submission
|
||||
control = dkim_disable_verify
|
||||
|
||||
require message = Relay not permitted
|
||||
domains = +local_domains
|
||||
|
||||
require verify = recipient
|
||||
|
||||
accept
|
||||
|
||||
acl_check_data:
|
||||
|
||||
accept
|
||||
|
||||
begin routers
|
||||
|
||||
dnslookup:
|
||||
driver = dnslookup
|
||||
domains = ! +local_domains
|
||||
transport = remote_smtp
|
||||
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
|
||||
headers_add = X-Cebulacamp-Smtp-Source: ''${dlfunc{${encryptor}/lib/libencryptor.so}{encryptor}{$sender_rcvhost}}
|
||||
headers_remove = Received
|
||||
no_more
|
||||
|
||||
aliases:
|
||||
driver = redirect
|
||||
allow_fail
|
||||
allow_defer
|
||||
data = ''${lookup{$local_part}lsearch{${aliasFile}} {$value@cebula.camp}}
|
||||
|
||||
ldap_user:
|
||||
driver = redirect
|
||||
data = ''${lookup ldap { user="cn=ldap-access,ou=users,dc=cebula,dc=camp" pass="''${readfile(/var/secrets/ldap-access)}" \
|
||||
ldap://10.88.0.1:389/ou=users,dc=cebula,dc=camp?\
|
||||
cn?sub?(cn=''${quote_ldap:$local_part})} {$value@$domain}}
|
||||
cannot_route_message = Unknown address
|
||||
more = false
|
||||
|
||||
localuser:
|
||||
driver = accept
|
||||
transport = local_delivery
|
||||
|
||||
begin transports
|
||||
|
||||
remote_smtp:
|
||||
driver = smtp
|
||||
|
||||
local_delivery:
|
||||
driver = lmtp
|
||||
socket = /var/run/dovecot2/lmtp
|
||||
batch_max = 200
|
||||
rcpt_include_affixes
|
||||
|
||||
|
||||
begin authenticators
|
||||
|
||||
PLAIN:
|
||||
driver = plaintext
|
||||
server_set_id = $auth2
|
||||
server_prompts = :
|
||||
server_advertise_condition = ''${if def:tls_cipher }
|
||||
server_condition = ''${if and{{ \
|
||||
!eq{}{$auth2} }{ \
|
||||
ldapauth{\
|
||||
user="cn=''${quote_ldap_dn:$auth2},ou=users,dc=cebula,dc=camp" \
|
||||
pass=''${quote:$auth3} \
|
||||
ldap://10.88.0.1/} }} }
|
||||
|
||||
LOGIN:
|
||||
driver = plaintext
|
||||
server_set_id = $auth1
|
||||
server_prompts = <| Username: | Password:
|
||||
server_advertise_condition = ''${if def:tls_cipher }
|
||||
server_condition = ''${if and{{ \
|
||||
!eq{}{$auth1} }{ \
|
||||
ldapauth{\
|
||||
user="cn=''${quote_ldap_dn:$auth1},ou=users,dc=cebula,dc=camp" \
|
||||
pass=''${quote:$auth2} \
|
||||
ldap://10.88.0.1/} }} }
|
||||
'';
|
||||
};
|
||||
|
||||
services.dovecot2 = {
|
||||
enable = true;
|
||||
mailUser = "vmail";
|
||||
mailGroup = "vmail";
|
||||
createMailUser = true;
|
||||
enableLmtp = true;
|
||||
sslServerCert = "${acmeDir}/fullchain.pem";
|
||||
sslServerKey = "${acmeDir}/key.pem";
|
||||
enablePAM = false;
|
||||
|
||||
extraConfig = ''
|
||||
userdb {
|
||||
driver = ldap
|
||||
args = /etc/dovecot/dovecot-users-ldap.conf.ext
|
||||
}
|
||||
passdb {
|
||||
driver = ldap
|
||||
args = /etc/dovecot/dovecot-pass-ldap.conf.ext
|
||||
}
|
||||
protocol lmtp {
|
||||
auth_username_format = %n
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.dovecot2.preStart = ''
|
||||
cat > /etc/dovecot/dovecot-users-ldap.conf.ext <<END
|
||||
user_attrs = \\
|
||||
=home=/var/mail/%{ldap:cn}, \\
|
||||
=mail=maildir:/var/mail/%{ldap:cn}/Maildir
|
||||
user_filter = (&(objectClass=inetOrgPerson)(cn=%u))
|
||||
iterate_attrs = =user=%{ldap:cn}
|
||||
iterate_filter = (objectClass=inetOrgPerson)
|
||||
base = ou=users,dc=cebula,dc=camp
|
||||
hosts = 10.88.0.1
|
||||
dn = cn=ldap-access,ou=users,dc=cebula,dc=camp
|
||||
dnpass = $(cat /var/secrets/ldap-access)
|
||||
END
|
||||
cat > /etc/dovecot/dovecot-pass-ldap.conf.ext <<END
|
||||
auth_bind = yes
|
||||
auth_bind_userdn = cn=%u,ou=users,dc=cebula,dc=camp
|
||||
base = ou=users,dc=cebula,dc=camp
|
||||
hosts = 10.88.0.1
|
||||
dn = cn=ldap-access,ou=users,dc=cebula,dc=camp
|
||||
dnpass = $(cat /var/secrets/ldap-access)
|
||||
END
|
||||
'';
|
||||
|
||||
services.roundcube = {
|
||||
enable = true;
|
||||
hostName = "mail.orga.cebula.camp";
|
||||
extraConfig = ''
|
||||
$config['default_host'] = 'tls://mail.orga.cebula.camp';
|
||||
$config['smtp_server'] = 'tls://mail.orga.cebula.camp';
|
||||
$config['mail_domain'] = 'cebula.camp';
|
||||
'';
|
||||
};
|
||||
|
||||
users.users.dovecot2.extraGroups = [ "ldap-access" ];
|
||||
users.users.exim.extraGroups = [ "ldap-access" ];
|
||||
}
|
||||
128
nextcloud.nix
Normal file
128
nextcloud.nix
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
|
||||
services.nextcloud = {
|
||||
enable = true;
|
||||
hostName = "cloud.orga.cebula.camp";
|
||||
package = pkgs.nextcloud30;
|
||||
settings = {
|
||||
overwriteprotocol = "https";
|
||||
trusted_proxies = [ "127.0.0.1" ];
|
||||
};
|
||||
config = {
|
||||
dbtype = "pgsql";
|
||||
dbname = "nextcloud";
|
||||
dbuser = "nextcloud";
|
||||
dbhost = "/run/postgresql";
|
||||
adminpassFile = "/opt/nextcloudpass";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."${config.services.nextcloud.hostName}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."office.orga.cebula.camp" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations = {
|
||||
"^~ /browser" = {
|
||||
proxyPass = "http://localhost:9980";
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
'';
|
||||
};
|
||||
|
||||
"^~ /hosting/discovery" = {
|
||||
proxyPass = "http://localhost:9980";
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
'';
|
||||
};
|
||||
|
||||
"^~ /hosting/capabilities" = {
|
||||
proxyPass = "http://localhost:9980";
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
'';
|
||||
};
|
||||
|
||||
"~ ^/cool" = {
|
||||
proxyPass = "http://localhost:9980";
|
||||
extraConfig = ''
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_read_timeout 36000s;
|
||||
'';
|
||||
};
|
||||
|
||||
"^~ ^/(c|l)ool" = {
|
||||
proxyPass = "http://localhost:9980";
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
'';
|
||||
};
|
||||
|
||||
"^~ /lool/adminws" = {
|
||||
proxyPass = "http://localhost:9980";
|
||||
extraConfig = ''
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_read_timeout 36000s;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql = let
|
||||
nc = config.services.nextcloud.config;
|
||||
in {
|
||||
enable = true;
|
||||
package = pkgs.postgresql_16;
|
||||
ensureDatabases = [ nc.dbname ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = nc.dbuser;
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers.collabora = {
|
||||
image = "collabora/code:24.04.11.3.1";
|
||||
ports = [ "127.0.0.1:9980:9980" ];
|
||||
environment = {
|
||||
extra_params = "--o:ssl.enable=false --o:ssl.termination=true";
|
||||
domain = "office.orga.cebula.camp";
|
||||
username = "admin";
|
||||
};
|
||||
environmentFiles = [
|
||||
"/var/secrets/collabora-admin-password"
|
||||
];
|
||||
};
|
||||
|
||||
#virtualisation.oci-containers.containers.office = {
|
||||
# image = "onlyoffice/documentserver:6.4.1.45";
|
||||
# ports = [ "127.0.0.1:8181:80" ];
|
||||
# volumes = [
|
||||
# "onlyoffice_logs:/var/log/onlyoffice"
|
||||
# "onlyoffice_data:/var/www/onlyoffide/Data"
|
||||
# "onlyoffice_cache:/var/lib/onlyoffice"
|
||||
# "onlyoffice_db:/var/lib/postgresql"
|
||||
# ];
|
||||
# environment = let
|
||||
# # Secret used to limit access to ONLYOFFICE from our nextcloud instance.
|
||||
# # This doesn't give access to any documents, and is just a shared key to
|
||||
# # ensure nothing but our nextcloud instance has access to the server.
|
||||
# secretKey = "…";
|
||||
# in {
|
||||
# JWT_ENABLED = "true";
|
||||
# JWT_SECRET = secretKey;
|
||||
# };
|
||||
#};
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue