summaryrefslogtreecommitdiffstats
path: root/aconf/modules
diff options
context:
space:
mode:
Diffstat (limited to 'aconf/modules')
-rw-r--r--aconf/modules/demo-awall.lua149
-rw-r--r--aconf/modules/demo-generic.lua14
-rw-r--r--aconf/modules/network.lua281
-rw-r--r--aconf/modules/openssh.lua28
4 files changed, 472 insertions, 0 deletions
diff --git a/aconf/modules/demo-awall.lua b/aconf/modules/demo-awall.lua
new file mode 100644
index 0000000..7d28989
--- /dev/null
+++ b/aconf/modules/demo-awall.lua
@@ -0,0 +1,149 @@
+--[[
+Copyright (c) 2012-2014 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+local M = require('aconf.model')
+local object = require('aconf.object')
+
+
+local Direction = object.class(M.String)
+function Direction:init(params)
+ if not params then params = {} end
+ params.choice = {'in', 'out'}
+ object.super(self, Direction):init(params)
+end
+
+
+-- TODO reference types?
+
+local IPSet = M.new()
+-- TODO choices
+IPSet.type = M.String{required=true}
+IPSet.family = M.String{required=true, choice={'inet', 'inet6'}}
+-- TODO only for bitmaps
+IPSet.range = M.Range{type=M.net.IPv4Address}
+
+local Service = M.new()
+Service.proto = M.String{required=true, ui_name='Protocol'}
+Service.port = M.Set{type=M.Range{type=M.net.Port}}
+Service.icmp_type = M.String{ui_name='ICMP type'}
+Service.ct_helper = M.String{ui_name='Connection tracking helper'}
+
+-- TODO fw zone
+
+local Zone = M.new()
+Zone.iface = M.Set{type=M.String, ui_name='Interfaces'}
+Zone.addr = M.Set{type=M.String, ui_name='Addresses'}
+Zone.route_back = M.Boolean{default=false}
+
+local LogClass = M.new()
+LogClass.mode = M.String{
+ required=true, default='log', choice={'log', 'nflog', 'ulog'}
+}
+LogClass.every = M.Integer{ui_name='Sampling frequency'}
+LogClass.limit = M.Integer
+LogClass.prefix = M.String
+LogClass.probability = M.Number
+LogClass.group = M.Integer
+LogClass.range = M.Integer
+LogClass.threshold = M.Integer
+
+local IPSetReference = M.new()
+IPSetReference.name = M.Reference{scope='/awall/ipset', required=true}
+IPSetReference.args = M.List{
+ type=Direction, required=true, ui_name='Arguments'
+}
+
+local Rule = M.new()
+Rule['in'] = M.Set{
+ type=M.Reference{scope='/awall/zone'}, ui_name='Ingress zones'
+}
+Rule.out = M.Set{
+ type=M.Reference{scope='/awall/zone'}, ui_name='Egress zones'
+}
+Rule.src = M.Set{type=M.String, ui_name='Sources'}
+Rule.dest = M.Set{type=M.String, ui_name='Destinations'}
+Rule.ipset = M.Model{model=IPSetReference, ui_name='IP set'}
+Rule.ipsec = Direction{ui_name='Require IPsec'}
+Rule.service = M.Set{type=M.Reference{scope='/awall/service'}}
+Rule.action = M.String{choice={'accept'}}
+
+
+local PacketLogRule = M.new(Rule)
+PacketLogRule.log = M.Reference{scope='../../log', ui_name='Log class'}
+
+-- TODO no service field
+local PolicyRule = M.new(PacketLogRule)
+PolicyRule.action = M.String{
+ required=true, choice={'accept', 'drop', 'reject', 'tarpit'}
+}
+
+local Limit = M.new()
+Limit.count = M.Integer
+Limit.interval = M.Integer
+Limit.log = M.Reference{scope='../../../log'}
+
+local FilterRule = M.new(PolicyRule)
+FilterRule.conn_limit = M.Model{model=Limit, ui_name='Connection limit'}
+FilterRule.flow_limit = M.Model{model=Limit, ui_name='Flow limit'}
+FilterRule.dnat = M.net.IPv4Address{ui_name='DNAT target'}
+FilterRule.no_track = M.Boolean{default=false, ui_name='CT bypass'}
+FilterRule.related = M.List{type=Rule, ui_name='Related packet rules'}
+
+local DivertRule = M.new(Rule)
+DivertRule.to_port = M.Range{type=M.net.Port, ui_name='Target port'}
+
+local NATRule = M.new(DivertRule)
+NATRule.to_addr = M.Range{type=M.net.IPv4Address, ui_name='Target address'}
+
+local MarkRule = M.new(Rule)
+MarkRule.mark = M.Integer{required=true}
+
+local ClampMSSRule = M.new(Rule)
+ClampMSSRule.mss = M.Integer{ui_name='MSS'}
+
+
+local AWall = M.new()
+-- TODO differentiate lists?
+AWall.service = M.Collection{type=M.List{type=Service}}
+AWall.zone = M.Collection{type=Zone}
+AWall.log = M.Collection{
+ type=LogClass, ui_name='Log classes', ui_member='Log class'
+}
+AWall.policy = M.List{type=PolicyRule, ui_name='Policies', ui_member='Policy'}
+AWall.packet_log = M.List{
+ type=PacketLogRule, ui_name='Logging', ui_member='Logging rule'
+}
+AWall.filter = M.List{type=FilterRule}
+AWall.dnat = M.List{type=NATRule, ui_name='DNAT', ui_member='DNAT rule'}
+AWall.snat = M.List{type=NATRule, ui_name='SNAT', ui_member='SNAT rule'}
+AWall.mark = M.List{
+ type=MarkRule, ui_name='Packet marking', ui_member='Packet marking rule'
+}
+AWall.route_track = M.List{
+ type=MarkRule, ui_name='Route tracking', ui_member='Route tracking rule'
+}
+AWall.tproxy = M.List{
+ type=DivertRule,
+ ui_name='Transparent proxy',
+ ui_member='Transparent proxy rule'
+}
+AWall.clamp_mss = M.List{
+ type=ClampMSSRule, ui_name='MSS clamping', ui_member='MSS clamping rule'
+}
+AWall.no_track = M.List{
+ type=Rule, ui_name='CT bypass', ui_member='Connection tracking bypass rule'
+}
+AWall.ipset = M.Collection{type=IPSet, ui_name='IP sets', ui_member='IP set'}
+
+M.register(
+ 'awall',
+ AWall,
+ {
+ addr='/json'..require('posix').getcwd()..'/config/awall.json',
+ ui_name='Alpine Wall'
+ }
+)
+
+M.permission.defaults('/awall')
diff --git a/aconf/modules/demo-generic.lua b/aconf/modules/demo-generic.lua
new file mode 100644
index 0000000..4e602fa
--- /dev/null
+++ b/aconf/modules/demo-generic.lua
@@ -0,0 +1,14 @@
+--[[
+Copyright (c) 2012-2014 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+-- provided as an example, to be removed from production version
+
+local M = require('aconf.model')
+
+M.register('proc', M.Mixed, {addr='/files/proc', ui_name='/proc'})
+M.permission.defaults('/proc')
+
+M.register('augeas', M.Mixed, {addr='/augeas'})
+M.permission.defaults('/augeas')
diff --git a/aconf/modules/network.lua b/aconf/modules/network.lua
new file mode 100644
index 0000000..ad772ed
--- /dev/null
+++ b/aconf/modules/network.lua
@@ -0,0 +1,281 @@
+--[[
+Copyright (c) 2012-2014 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+local M = require('aconf.model')
+
+local posix = require('posix')
+
+
+local Host = M.new()
+Host.address = M.net.IPAddress{addr='ipaddr'}
+Host.canonical = M.String{ui_name='Canonical name'}
+Host.alias = M.Set{
+ type=M.String,
+ addr='alias/#',
+ ui_name='Aliases',
+ ui_member='Alias',
+ detail=false
+}
+
+local Resolv = M.new()
+Resolv.servers = M.List{
+ type=M.net.IPAddress, addr='nameserver/#', widget='inline'
+}
+Resolv.search_domains = M.List{
+ type=M.String, addr='search/domain/#', widget='inline'
+}
+
+
+local iface_aug_addr = '/augeas/etc/network/interfaces'
+local iface_sys_dir = '/sys/class/net'
+local vlan_pattern = '^(.+)%.(%d+)$'
+
+for _, trigger in ipairs{
+ {phase='pre', action='stop'}, {phase='post', action='start'}
+} do
+ M.trigger(
+ trigger.phase,
+ iface_aug_addr,
+ function() os.execute('rc-service networking '..trigger.action) end
+ )
+end
+
+M.defer(iface_aug_addr)
+
+
+-- TODO allow multiple addresses of same family
+
+local IPv4 = M.new()
+IPv4.method = M.String{
+ required=true,
+ choice={
+ {'dhcp', 'DHCP'},
+ {'loopback', enabled=false},
+ {'unconfigured', be_value='manual'},
+ 'static'
+ },
+ default='unconfigured'
+}
+IPv4.address = M.net.IPv4Address{
+ condition={method='static'}, required=true, cidr=true, mask_addr='netmask'
+}
+IPv4.gateway = M.net.IPv4Address{condition={method='static'}}
+
+
+local IPv6 = M.new()
+IPv6.method = M.String{
+ required=true,
+ choice={
+ {'loopback', enabled=false}, {'unconfigured', be_value='manual'}, 'static'
+ },
+ default='unconfigured'
+}
+IPv6.address = M.net.IPv6Address{
+ condition={method='static'}, required=true, cidr=true, mask_addr='netmask'
+}
+IPv6.gateway = M.net.IPv6Address{condition={method='static'}}
+
+
+
+local Interface = M.new()
+
+function Interface:validate()
+ if self.status == 'attached' then
+ for _, version in ipairs{4, 6} do
+ self['ipv'..version].method =
+ self.class == 'loopback' and 'loopback' or 'unconfigured'
+ end
+ end
+end
+
+function Interface:is_removable() return self.class == 'logical' end
+
+function Interface:auto_set()
+ for _, set in M.node.ipairs(self:fetch('../../enabled-ifaces')) do
+ if M.node.contains(set, self) then return set end
+ end
+end
+
+function Interface:auto_vlan_tag()
+ local name = M.node.name(self)
+ local _, tag = name:match(vlan_pattern)
+ if tag then return tag end
+ return name:match('^vlan(%d+)$')
+end
+
+Interface.enabled = M.Boolean{
+ compute=function(self, iface) return iface:auto_set() and true or false end,
+ store=function(self, iface, value)
+ local set = iface:auto_set()
+ if value and not set then
+ M.node.insert(iface:fetch('../../enabled-ifaces/1'), iface)
+ elseif not value and set then set[iface] = nil end
+ end
+}
+
+Interface.class = M.String{
+ compute=function(self, iface, txn)
+ local name = M.node.name(iface)
+ if name == 'lo' then return 'loopback' end
+
+ local saddr = M.path.rawjoin('/files', iface_sys_dir, name, '.')
+ if not txn:get(saddr) then return 'logical' end
+
+ for _, addr in ipairs{
+ {saddr, 'bonding'},
+ {saddr, 'bridge'},
+ {'/files/proc/net/vlan', name}
+ } do
+ if txn:get(M.path.join(unpack(addr))) then
+ return 'logical'
+ end
+ end
+ return 'physical'
+ end,
+ choice={'loopback', 'logical', 'physical'}
+}
+
+Interface.type = M.String{
+ condition={class='logical'},
+ compute=function(self, iface)
+ if #iface.slaves > 0 then return 'bond' end
+ if #iface.ports > 0 then return 'bridge' end
+ if iface.vlan_tag then return 'vlan' end
+ end,
+ editable=function(self, iface) return not iface:auto_vlan_tag() end,
+ required=true,
+ choice={'bond', 'bridge', {'vlan', 'VLAN'}}
+}
+
+Interface.status = M.String{
+ visible=false,
+ compute=function(self, obj)
+ if obj.class == 'loopback' then return 'attached' end
+
+ for _, iface in M.node.pairs(M.node.parent(obj)) do
+ if (
+ iface.type == 'bond' and M.node.contains(iface.slaves, obj)
+ ) or (
+ iface.type == 'bridge' and M.node.contains(iface.ports, obj)
+ ) then
+ return 'attached'
+ end
+
+ if iface.type == 'vlan' and iface.trunk == obj then
+ return 'configured'
+ end
+ end
+
+ for _, version in ipairs{4, 6} do
+ if obj['ipv'..version].method ~= 'unconfigured' then
+ return 'configured'
+ end
+ end
+
+ return 'detached'
+ end
+}
+
+Interface.slaves = M.Set{
+ condition={type='bond'},
+ type=M.Reference{scope='../..', filter={status='detached'}},
+ required=true,
+ addr='@family/link/@method/none/bond-slaves'
+}
+Interface.ports = M.Set{
+ condition={type='bridge'},
+ type=M.Reference{scope='../..', filter={status='detached'}},
+ required=true,
+ addr='@family/link/@method/none/bridge-ports'
+}
+-- TODO do not allow VLAN creation for non-existent interfaces
+Interface.trunk = M.Reference{
+ condition={type='vlan'},
+ compute=function(self, iface)
+ local trunk = M.node.name(iface):match(vlan_pattern)
+ if trunk and iface:fetch('..')[trunk] then return trunk end
+ end,
+ required=true,
+ scope='..',
+ filter={status={'detached', 'configured'}},
+ addr='@family/link/@method/none/vlan-raw-device'
+}
+-- TODO ensure that (trunk, tag) is unique
+Interface.vlan_tag = M.Integer{
+ condition={type='vlan'},
+ compute='auto_vlan_tag',
+ required=true,
+ min=0,
+ max=4095,
+ addr='@family/link/@method/none/vlan-id',
+ ui_name='VLAN tag'
+}
+
+Interface.ipv4 = M.Model{
+ model=IPv4,
+ condition={status={'detached', 'configured'}},
+ create=true,
+ addr='@family/inet',
+ ui_name='IPv4 configuration',
+ widget='inline'
+}
+Interface.ipv6 = M.Model{
+ model=IPv6,
+ condition={status={'detached', 'configured'}},
+ create=true,
+ addr='@family/inet6',
+ ui_name='IPv6 configuration',
+ widget='inline'
+}
+
+Interface.stats = M.Collection{
+ type=M.Number{editable=false},
+ editable=false,
+ addr=function(path)
+ return M.path.join(
+ '/files/sys/class/net', M.path.name(path), 'statistics'
+ )
+ end,
+ ui_name='Statistics',
+ ui_member='',
+ widget='inline'
+}
+
+
+local Net = M.new()
+Net.host_name = M.String{addr='/augeas/etc/hostname/hostname'}
+Net.hosts = M.List{type=Host, addr='/augeas/etc/hosts'}
+Net.resolver = M.Model{
+ model=Resolv, addr='/augeas/etc/resolv.conf', ui_name='DNS resolver'
+}
+
+Net.enabled_ifaces = M.List{
+ type=M.Set{type=M.Reference{scope='../../interfaces', on_delete='set-null'}},
+ visible=false,
+ addr=iface_aug_addr..'/auto/#'
+}
+Net.interfaces = M.Collection{
+ type=Interface, addr=iface_aug_addr..'/iface/@', widget='inline'
+}
+
+M.register('net', Net, {ui_name='Network'})
+M.permission.defaults('/net')
+
+return function(txn)
+ local ifaces = txn:fetch('/net/interfaces')
+ for _, name in ipairs(posix.dir(iface_sys_dir)) do
+ if not ifaces[name] and posix.stat(
+ M.path.join(iface_sys_dir, name), 'type'
+ ) == 'link' then
+ ifaces[name] = {}
+ if ifaces[name].class == 'logical' then ifaces[name] = nil
+ else
+ for _, version in ipairs{4, 6} do
+ ifaces[name]['ipv'..version].method = 'unconfigured'
+ end
+ end
+ end
+ end
+ end
diff --git a/aconf/modules/openssh.lua b/aconf/modules/openssh.lua
new file mode 100644
index 0000000..5ca2544
--- /dev/null
+++ b/aconf/modules/openssh.lua
@@ -0,0 +1,28 @@
+--[[
+Copyright (c) 2013 Natanael Copa <ncopa@alpinelinux.org>
+Copyright (c) 2013-2014 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+local M = require('aconf.model')
+
+local Sshd = M.service('sshd')
+Sshd.root_login = M.Boolean{
+ addr='PermitRootLogin', ui_name='Permit root login', default=true
+}
+Sshd.password_auth = M.Boolean{
+ addr='PasswordAuthentication',
+ ui_name='Password authentication',
+ default=true
+}
+Sshd.use_dns = M.Boolean{addr='UseDNS', ui_name='Use DNS', default=true}
+Sshd.agent_forward = M.Boolean{
+ addr='AllowAgentForwarding', ui_name='Allow agent forwarding', default=true
+}
+
+M.register(
+ 'sshd',
+ Sshd,
+ {addr='/augeas/etc/ssh/sshd_config', ui_name='SSH daemon'}
+)
+M.permission.defaults('/sshd')