Initial commit.

This commit is contained in:
mewp 2025-01-20 12:24:59 +00:00 committed by mewp
commit 9f27e056d5
21 changed files with 1439 additions and 0 deletions

25
.gitignore vendored Normal file
View 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
View 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
View 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. Its 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
View 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
View file

@ -0,0 +1,6 @@
[workspace]
members = [
"encryptor",
"decryptor",
"common",
]

View 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
View 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");
}

View 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;
}

View 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);

View file

@ -0,0 +1 @@
pub mod smtp;

View 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()
})
}

View file

@ -0,0 +1,10 @@
[package]
name = "decryptor"
version = "0.1.0"
edition = "2018"
[dependencies]
common = { path = "../common" }
protobuf = "2"
secretbox = "0"
base64 = "0"

View 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);
}

View 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"

View 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
View 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
'';
}

View file

@ -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
View 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>

View 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
View 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
View 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;
# };
#};
}