From 3d3f3d4525a823d243d3254a3c03a194ba252394 Mon Sep 17 00:00:00 2001 From: Kaarle Ritvanen Date: Wed, 12 Feb 2014 12:11:28 +0200 Subject: Makefile: include network and openssh modules --- Makefile | 9 +- acf2/modules/awall.lua | 149 ----------------------- acf2/modules/demo-awall.lua | 149 +++++++++++++++++++++++ acf2/modules/demo-generic.lua | 14 +++ acf2/modules/generic.lua | 14 --- acf2/modules/net.lua | 270 ------------------------------------------ acf2/modules/network.lua | 270 ++++++++++++++++++++++++++++++++++++++++++ acf2/modules/openssh.lua | 28 +++++ acf2/modules/sshd.lua | 28 ----- 9 files changed, 464 insertions(+), 467 deletions(-) delete mode 100644 acf2/modules/awall.lua create mode 100644 acf2/modules/demo-awall.lua create mode 100644 acf2/modules/demo-generic.lua delete mode 100644 acf2/modules/generic.lua delete mode 100644 acf2/modules/net.lua create mode 100644 acf2/modules/network.lua create mode 100644 acf2/modules/openssh.lua delete mode 100644 acf2/modules/sshd.lua diff --git a/Makefile b/Makefile index 063faa2..ae208a5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# Copyright (c) 2012-2013 Kaarle Ritvanen +# Copyright (c) 2012-2014 Kaarle Ritvanen # See LICENSE file for license details ROOT_DIR := / @@ -41,10 +41,7 @@ endef $(eval $(call link,acf2,$(luadir))) -$(eval $(call rcopy,acf2,$(luadir),lua,-not -path 'acf2/modules/*')) - -$(ROOT_DIR)/$(luadir)/modules: - install -d $@ +$(eval $(call rcopy,acf2,$(luadir),lua,-not -path 'acf2/modules/demo-*')) $(eval $(call link,config,$(confdir))) $(eval $(call copy,config,$(confdir),aaa.json,600)) @@ -62,6 +59,6 @@ $(eval $(call copy,web,$(webdir),client.html)) $(eval $(call rcopy,web,$(webdir),js)) -install: $(foreach f,$(files),$(ROOT_DIR)/$(f)) $(ROOT_DIR)/$(luadir)/modules $(ROOT_DIR)/$(starter) +install: $(foreach f,$(files),$(ROOT_DIR)/$(f)) $(ROOT_DIR)/$(starter) .PHONY: all install diff --git a/acf2/modules/awall.lua b/acf2/modules/awall.lua deleted file mode 100644 index 45c3674..0000000 --- a/acf2/modules/awall.lua +++ /dev/null @@ -1,149 +0,0 @@ ---[[ -Copyright (c) 2012-2014 Kaarle Ritvanen -See LICENSE file for license details ---]] - -local M = require('acf2.model') -local object = require('acf2.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/acf2/modules/demo-awall.lua b/acf2/modules/demo-awall.lua new file mode 100644 index 0000000..45c3674 --- /dev/null +++ b/acf2/modules/demo-awall.lua @@ -0,0 +1,149 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local M = require('acf2.model') +local object = require('acf2.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/acf2/modules/demo-generic.lua b/acf2/modules/demo-generic.lua new file mode 100644 index 0000000..254add8 --- /dev/null +++ b/acf2/modules/demo-generic.lua @@ -0,0 +1,14 @@ +--[[ +Copyright (c) 2012-2013 Kaarle Ritvanen +See LICENSE file for license details +--]] + +-- provided as an example, to be removed from production version + +local M = require('acf2.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/acf2/modules/generic.lua b/acf2/modules/generic.lua deleted file mode 100644 index 254add8..0000000 --- a/acf2/modules/generic.lua +++ /dev/null @@ -1,14 +0,0 @@ ---[[ -Copyright (c) 2012-2013 Kaarle Ritvanen -See LICENSE file for license details ---]] - --- provided as an example, to be removed from production version - -local M = require('acf2.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/acf2/modules/net.lua b/acf2/modules/net.lua deleted file mode 100644 index 32b9d20..0000000 --- a/acf2/modules/net.lua +++ /dev/null @@ -1,270 +0,0 @@ ---[[ -Copyright (c) 2012-2014 Kaarle Ritvanen -See LICENSE file for license details ---]] - -local M = require('acf2.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' -} - -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, - default='manual', - choice={{'dhcp', 'DHCP'}, {'loopback', enabled=false}, 'manual', 'static'} -} -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}, 'manual', 'static'}, - default='manual' -} -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 'manual' - 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 ~= 'manual' 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', layout='tabular'} -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 = 'manual' - end - end - end - end - end diff --git a/acf2/modules/network.lua b/acf2/modules/network.lua new file mode 100644 index 0000000..32b9d20 --- /dev/null +++ b/acf2/modules/network.lua @@ -0,0 +1,270 @@ +--[[ +Copyright (c) 2012-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local M = require('acf2.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' +} + +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, + default='manual', + choice={{'dhcp', 'DHCP'}, {'loopback', enabled=false}, 'manual', 'static'} +} +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}, 'manual', 'static'}, + default='manual' +} +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 'manual' + 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 ~= 'manual' 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', layout='tabular'} +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 = 'manual' + end + end + end + end + end diff --git a/acf2/modules/openssh.lua b/acf2/modules/openssh.lua new file mode 100644 index 0000000..458d2d7 --- /dev/null +++ b/acf2/modules/openssh.lua @@ -0,0 +1,28 @@ +--[[ +Copyright (c) 2013 Natanael Copa +Copyright (c) 2013-2014 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local M = require('acf2.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') diff --git a/acf2/modules/sshd.lua b/acf2/modules/sshd.lua deleted file mode 100644 index 458d2d7..0000000 --- a/acf2/modules/sshd.lua +++ /dev/null @@ -1,28 +0,0 @@ ---[[ -Copyright (c) 2013 Natanael Copa -Copyright (c) 2013-2014 Kaarle Ritvanen -See LICENSE file for license details ---]] - -local M = require('acf2.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') -- cgit v1.2.3