Small Office Services: Difference between revisions

From Alpine Linux
(use relative symlinks)
 
(56 intermediate revisions by 7 users not shown)
Line 1: Line 1:
{{Draft}}
'''Abstract''': This document will outline how to provide various network services for a small remote office, using Linux containerization (LXC).


{{Tip|At the time of writing this document the recommended Alpine version for building the Host box for the containers should be at minimum 2.7.3 64 bit.}}
'''Abstract''': This document will outline how to provide various network services for a small remote office, using Linux containerization (LXC).  It is designed to be a complement to the [[Dynamic Multipoint VPN (DMVPN)|DMVPN]] spoke node.
 
The following services will be available in addition to the encrypted communications between offices provided by the DMVPN network:
* Internet browsing proxy server with domain filtering (wired clients on protected internal network)
* Separate proxy for wifi clients
* SIP phone system including web based provisioning and basic voicemail services
 
The assumption is made that the following VLANs and subnets are used as the DMVPN document did:
 
{|class="wikitable"
!'''Interface'''
!'''Description'''
!'''Subnet'''
|-
|bond0.3
|Management
|10.1.0.129/26
|-
|bond0.101
|LAN
|10.1.0.0/25
|-
|bond0.256
|Internet from ISP1
|Allocated from ISP
|-
|bond0.257
|Internet from ISP2
|Allocated from ISP
|-
|bond0.620
|Transit between wifi proxy and dmvpn spoke node
|10.1.0.252/30
|-
|bond0.701
|WiFi clients (no access to DMVPN network)
|172.17.48.0/24
|-
|bond0.1101
|Voice
|10.2.0.0/24
|}
 
 
{{Tip|At the time of writing, the recommended Alpine version for building the Host box for the containers should be at minimum 2.7.9 64 bit.}}


= Hardware =
= Hardware =
For an office that will serve under 20 people, the following containers can easily run on low-power hardware such as a Via Nano 1.6Ghz Jetway board with 8GB RAM with dual 500GB SATA hard drives running in RAID 1 (software).
= Setup LXC Host Box =
= Setup LXC Host Box =


== Boot Alpine USB ==  
== Boot Alpine USB ==  
Follow the instructions on  http://wiki.alpinelinux.org/wiki/Create_a_Bootable_USB about how to create a bootable USB.
Follow the instructions at [[Create a Bootable Device]].


== Alpine Setup ==
== Alpine Setup ==
Line 62: Line 106:
|''Press Enter confirming 'UTC'''
|''Press Enter confirming 'UTC'''
|-
|-
|<code>HTTP/FTP proxy URL? (e.g. 'http://proxy:8080', or 'none') [none]</code>
|<code>HTTP/FTP proxy URL? (e.g. '<nowiki>http://proxy:8080</nowiki>', or 'none') [none]</code>
|''http://'''<%DMVPN_LAN_IP%>''':8080''
|''http://'''<%DMVPN_LAN_IP%>''':8080''
|-
|-
Line 106: Line 150:
iface bond0 inet manual
iface bond0 inet manual
         bond-slaves eth0 eth1
         bond-slaves eth0 eth1
        bond-mode balance-tlb
        bond-miimon 100
        bond-updelay 500
         up ip link set $IFACE up
         up ip link set $IFACE up
        up bond-mode balance-tlb
        up bond-miimon 100
         down ip link set $IFACE down
         down ip link set $IFACE down


Line 128: Line 173:


auto bond0.701
auto bond0.701
iface bond0.601 inet manual
iface bond0.701 inet manual
up ip link set $IFACE up
up ip link set $IFACE up
down ip link set $IFACE down
down ip link set $IFACE down
Line 134: Line 179:


Apply changes by restarting networking
Apply changes by restarting networking
{{Cmd|/etc/init.d/networking restart}}
{{Cmd|rc-service networking restart}}


== Enable IP Forwarding ==
== Enable IP Forwarding ==
Line 141: Line 186:
{{Cmd|apk add acf-awall}}
{{Cmd|apk add acf-awall}}


With your favorite editor, create a base policy file for a wall, /etc/awall/optional/base.json
With your favorite editor, create the base policy for the firewall
{{cat|/etc/awall/optional/base.json|
{{cat|/etc/awall/optional/base.json|
{
{
   "description": "Base Policy",
   "description": "Management",


   "policy": [
   "policy": [
Line 159: Line 204:
}
}
}}
}}
Activate the Firewall
Activate the firewall, and allow iptables to startup automatically at boot
{{Cmd|modprobe ip_tables
{{Cmd|modprobe ip_tables
awall enable base
awall enable base
awall activate
awall activate -f
}}
rc-update add iptables}}
Configure ip_tables to start automatically when host is booted up
{{Cmd| rc-update add awall}}


== Install LXC ==
== Install LXC ==
Line 191: Line 234:
{{Cmd|lxc-create -n webproxy -f /etc/lxc/default.conf -t alpine}}
{{Cmd|lxc-create -n webproxy -f /etc/lxc/default.conf -t alpine}}
Create the startup Script
Create the startup Script
{{Cmd|ln -s /etc/init.d/lxc /etc/init.d/lxc.webproxy}}
{{Cmd|ln -s lxc /etc/init.d/lxc.webproxy}}


Edit the container's config file found at /var/lib/lxc/webproxy/config, to reflect the network for the web proxy container
Edit the container's config file found at /var/lib/lxc/webproxy/config, to reflect the network for the web proxy container
Line 202: Line 245:


Start the container
Start the container
{{Cmd|/etc/iniit.d/lxc.webproxy}}
{{Cmd|rc-service lxc.webproxy start}}


Configure the container to automatically start
Configure the container to automatically start
Line 210: Line 253:
{{Cmd|lxc-console -n webproxy}}
{{Cmd|lxc-console -n webproxy}}
Login as root
Login as root
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| k}}}}
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| q}}}}
Remove obsolete /etc/network/interfaces
Remove obsolete /etc/network/interfaces
{{Cmd|rm /etc/network/interfaces}}
{{Cmd|rm /etc/network/interfaces}}
Line 220: Line 263:
auto eth0
auto eth0
iface eth0 inet static
iface eth0 inet static
address <%WEB_PROXY_IP_ADDRESS%>
address 10.1.0.2
netmask <%DMVPN_LAN_NETMASK%>
netmask 255.255.255.192
gateway <%DMVPN_LAN_IP_ADDRESS%>
gateway 10.1.0.1
}}
}}


Startup networking  
Startup networking  
{{Cmd| /etc/init.d/networking start}}
{{Cmd| rc-service networking start}}


Add rule to DMVPN awall policy to allow this proxy out to the internet
Add rule to DMVPN awall policy to allow this proxy out to the internet
Line 233: Line 276:
{
{
     "in": "B",
     "in": "B",
       "src": "$WEB_PROXY",
       "src": "$10.1.0.2",
       "out": "E",
       "out": "E",
       "action": "accept",
       "action": "accept",
Line 246: Line 289:


Start ssh
Start ssh
{{Cmd|/etc/init.d/sshd start}}
{{Cmd|rc-service sshd start}}


Configure a passwd for the container
Configure a passwd for the container
Line 255: Line 298:


== Setup Firewall ==
== Setup Firewall ==
Create the policies for the firewall
{{Cmd|apk add acf-awall}}
 
With your favorite editor, create the policies for the firewall
{{cat|/etc/awall/optional/base.json|
{{cat|/etc/awall/optional/base.json|
{
{
Line 289: Line 334:
{{Cmd|awall enable base
{{Cmd|awall enable base
awall enable webproxy
awall enable webproxy
awall activate
awall activate -f
rc-update add iptables
rc-update add iptables
}}
}}
Line 298: Line 343:


Configure /etc/squid/squid.conf, replace <%WEBPROXY_IP_ADDRESS%>, <%HOSTNAME%>, and <%DOMAIN%>
Configure /etc/squid/squid.conf, replace <%WEBPROXY_IP_ADDRESS%>, <%HOSTNAME%>, and <%DOMAIN%>
{{cat|/etc/init.d/squid/squid.conf|
{{cat|/etc/squid/squid.conf|
<pre>
<pre>
#Squid config for webproxy
#Squid config for webproxy
Line 521: Line 566:


Start lighttpd, and configure the service to start on when container is booted
Start lighttpd, and configure the service to start on when container is booted
{{Cmd|/etc/init.d/lighttpd start
{{Cmd|rc-service lighttpd start
rc-update add lighttpd}}
rc-update add lighttpd}}




Start Squid, and configure to start at boot
Start Squid, and configure to start at boot
{{Cmd|/etc/init.d/squid start
{{Cmd|rc-service squid start
rc-update add squid}}
rc-update add squid}}


= Install the DHCP and DNS server (netserv) Container =
= Install the DHCP and DNS server Container =
{{Todo|Needs revisit to restructure WiFi network's behavior}}
== Create and Configure the container ==
== Create and Configure the container ==
{{Cmd|lxc-create -n netserv -f /etc/lxc/default.conf -t alpine}}
{{Cmd|lxc-create -n dhcpdns -f /etc/lxc/default.conf -t alpine}}
Create the startup Script
Create the startup Script
{{Cmd|ln -s /etc/init.d/lxc /etc/init.d/lxc.netserv
{{Cmd|ln -s lxc /etc/init.d/lxc.dhcpdns}}


Edit the container's config file found at /var/lib/lxc/netserv/config, to reflect the network for the web proxy container
Edit the container's config file found at /var/lib/lxc/dhcpdns/config, to reflect the network for the web proxy container


{{cat|/var/lib/lxc/netserv/config|
{{cat|/var/lib/lxc/dhcpdns/config|
<pre>
<pre>
## Allow containers in the same VLAN to see each other
#Management Network Config
lxc.network.type = macvlan
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.macvlan.mode = bridge
lxc.network.link = bond0.3
lxc.network.link = bond0.3
lxc.network.name = eth0
lxc.network.name = eth0
#Management Network Config
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.link = bond0.3
lxc.network.name = eth_3
#LAN Network Config
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.link = bond0.101
lxc.network.name = eth_101


#WiFi Network Config
#WiFi Network Config
Line 562: Line 594:
lxc.network.macvlan.mode = bridge
lxc.network.macvlan.mode = bridge
lxc.network.link = bond0.701
lxc.network.link = bond0.701
lxc.network.name = eth_701
lxc.network.name = eth1


#Voice Network Config
#Voice Network Config
Line 568: Line 600:
lxc.network.macvlan.mode = bridge
lxc.network.macvlan.mode = bridge
lxc.network.link = bond0.1101
lxc.network.link = bond0.1101
lxc.network.name = eth_1101
lxc.network.name = eth2
</pre>
</pre>
}}
}}


Start the container
Start the container
{{Cmd|/etc/iniit.d/lxc.netserv}}
{{Cmd|rc-service lxc.dhcpdns start}}


Configure the container to automatically start
Configure the container to automatically start
{{Cmd|rc-update add lxc.netserv}}
{{Cmd|rc-update add lxc.dhcpdns}}


== Enter the netserv container ==
== Enter the dhcpdns container ==
{{Cmd|lxc-console -n netserv}}
{{Cmd|lxc-console -n dhcpdns}}
Login as root
Login as root
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| k}}}}
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| q}}}}
Remove obsolete /etc/network/interfaces
Remove obsolete /etc/network/interfaces
{{Cmd|rm /etc/network/interfaces}}
{{Cmd|rm /etc/network/interfaces}}
Line 590: Line 622:


#Management VLAN
#Management VLAN
auto eth_3
auto eth0
iface eth_3 inet static
iface eth0 inet static
       address <%MANAGEMENT_IP_ADDRESS%>
       address 10.1.0.130
       netmask <%DMVPN_MANAGEMENT_NETMASK%>
       netmask 255.255.255.192
 
#WiFi VLAN
auto eth1
iface eth1 inet static
      address 172.16.48.2
      netmask 255.255.255.0


#Voice VLAN
#Voice VLAN
auto eth_1101
auto eth2
iface eth_1101 inet static
iface eth2 inet static
       address <%NETSERV_VOICE_IP_ADDRESS%>
       address 10.2.0.2
       netmask <%DMVPN_VOICE_NETMASK%>
       netmask 255.255.255.0
       gateway <%DMVPN_VOICE_IP_ADDRESS%>
       gateway 10.2.0.1
 
       up ip address add 10.2.0.3/24 dev eth0
#LAN VLAN
   
auto eth_101
iface eth_101 inet static
      address <%LAN_IP_ADDRESS%>
      netmask <%DMVPN_LAN_NETMASK%>
 
#WiFi VLAN
auto eth_701
iface eth_701 inet static
       address <%NETSERV_WIFI_IP_ADDRESS%>
      netmask <%VPNC_WIFI_NETMASK%>
}}
}}


Startup networking  
Startup networking  
{{Cmd| /etc/init.d/networking start}}
{{Cmd| rc-service networking start}}


Configure and enable proxy settings
Configure and enable proxy settings
{{Cmd|setup-proxy http://<%WEBPROXY_IP_ADDRESS%>:8080
{{Cmd|setup-proxy <nowiki>http://10.1.0.2:8080</nowiki>
. /etc/profile.d/proxy.sh}}
. /etc/profile.d/proxy.sh}}


Line 629: Line 657:


Start ssh
Start ssh
{{Cmd|/etc/init.d/sshd start}}
{{Cmd|rc-service sshd start}}


Configure a passwd for the container
Configure a passwd for the container
Line 638: Line 666:


== Setup Firewall ==
== Setup Firewall ==
{{Todo|Need to setup Firewall rules}}
{{Cmd|apk add acf-awall}}
 
With your favorite editor, create the policies for the firewall
{{cat|/etc/awall/optional/base.json|
{
  "description": "Management",
 
  "policy": [
    { "in": "_fw", "action": "accept" }
  ],
 
  "filter": [
    {
      "out": "_fw",
      "service": [ "ssh", "https", "ping" ],
      "action": "accept"
    }
  ]
}
}}
{{cat|/etc/awall/optional/dhcp.json|
{
    "description": "DHCP",
    "filter": [
      {
        "out": "_fw",
        "service": "dhcp",
        "action": "accept"
      }
    ]
}
}}
{{cat|/etc/awall/optional/dns.json|
{
    "description": "DNS",
    "filter": [
      {
        "out": "_fw",
        "service": "dns",
        "action": "accept"
      }
    ]
}
}}
Activate the firewall, and allow iptables to startup automatically at boot
{{Cmd|awall enable base
awall enable dhcp
awall enable dns
awall activate -f
rc-update add iptables
}}


== Install and Configure DHCP and DNS services ==
== Install and Configure DHCP and DNS services ==
Line 654: Line 734:


## Common options
## Common options
option domain-name-servers <%DMVPN_LAN_IP_ADDRESS%>;
option time-servers 10.2.0.1;
option domain-name "location.example.net";
option time-servers <%DMVPN_VOICE_IP_ADDRESS%>;
option boot-server code 66 = string;
option boot-server code 66 = string;


## Voice
## Voice
subnet <%VOICE_SUBNET%> netmask <%VOICE_NETMASK%>
subnet 10.2.0.0 netmask 255.255.255.0
{
{
   range <%VOICE_DHCP_RANGE%>;
   range "10.2.0.20 10.2.0.250";
   option domain-name-servers <%NETSERV_VOICE_IP_ADDRESS%>;
   option domain-name-servers 10.2.0.2;
   option routers <%DMVPN_VOICE_IP_ADDRESS%>;
   option routers 10.2.0.1;
   option boot-server "http://<%SIP_IP_ADDRESS%>";
   option boot-server "http://10.2.0.4";
  option domain-name "office.example.net";
}
}


## WiFi
## WiFi
subnet <%WIFI_SUBNET%> netmask <%WIFI_NETMASK%>
subnet 172.17.48.0 netmask 255.255.255.0
{
{
   range <%WIFI_DHCP_RANGE%>;
   range "172.17.48.10 172.17.48.250";
   option routers <%WIFI_PROXY_IP_ADDRESS%>;
   option routers 172.17.48.1;
   option domain-name-servers <%NETSERV_WIFI_IP_ADDRESS%>;   
   option domain-name-servers 172.17.48.1;   
  option domain-name "<%WIFI_DOMAIN%>";
}
}
</pre>
</pre>
Line 693: Line 771:


server:
server:
         interface: <%NETSERV_WIFI_IP_ADDRESS%>
         interface: 10.2.0.2
        interface: <%NETSERV_LAN_IP_ADDRESS%>
         do-not-query-localhost: no
         do-not-query-localhost: no
         verbosity: 1
         verbosity: 1
Line 709: Line 786:


stub-zone:
stub-zone:
name: "location1.example.net"
name: "office.example.net"
stub-addr: 127.0.0.1
stub-addr: 10.2.0.3


stub-zone:
stub-zone:
Line 730: Line 807:
         stub-addr: 172.16.255.7
         stub-addr: 172.16.255.7


python:
remote-control:
        control-enable: no
}}
}}
Start Unbound and allow the container to use it
Start Unbound and allow the container to use it
{{Cmd|/etc/init.d/unbound start
{{Cmd|rc-service unbound start
rc-update add unbound
rc-update add unbound
echo nameserver <%NETSERV_LAN_IP_ADDRESS%> > /etc/resolv.conf
echo nameserver 10.2.0.2 > /etc/resolv.conf


Install nsd
Install nsd
Line 744: Line 818:
{{cat|/etc/nsd/nsd.conf|
{{cat|/etc/nsd/nsd.conf|
server:
server:
         ip-address: 127.0.0.1
         ip-address: 10.2.0.3
         port: 53
         port: 53
         server-count: 1
         server-count: 1
Line 771: Line 845:
@        NS      ns1
@        NS      ns1
; NSA Servers
; NSA Servers
ns1      IN      A      <%NETSERV_VOICE_IP_ADDRESS%>
ns1      IN      A      10.2.0.3


;A Records for SIP Devices
;A Records for SIP Devices
sip    IN      A      <%SIP_IP_ADDRESS%>
sip    IN      A      10.2.0.4
map     IN      A      <%VMAIL_IP_ADDRESS%>
media     IN      A      10.2.0.5


;NAPTR Records
;NAPTR Records
@       IN    NAPTR  10      1      "s"    "SIP+D2U"  "" _sip._udp.sip.office.example.net.
sip       IN    NAPTR  10      1      "s"    "SIP+D2U"  "" _sip._udp.sip.office.example.net.
@        IN    NAPTR  10      1      "s"    "SIP+D2U"  "" _sip._udp.vmail.office.example.net.


;SIP SRV Record
;SIP SRV Record
_sip._udp  IN  SRV  10  1   5060    sip
_sip._udp.sip IN  SRV  10  100   5060    sip
_sip._udp  IN  SRV  10  1    5060    vmail
}}
}}


Check nsd configuration and start service
Check nsd configuration and start service
{{Cmd|nsd-checkconf /etc/nsd/nsd.conf
{{Cmd|nsd-checkconf /etc/nsd/nsd.conf
nsdc rebuild
rc-service nsd start
/etc/init.d/nsd start
rc-update add nsd}}
rc-update add nsd}}


= Install the SIP Container =
= Install the SIP Container =
== Create and Configure the container ==
== Create and Configure the container ==
{{Cmd|lxc-create -n sip -f /etc/lxc/default.conf -t alpine}}
{{Cmd|lxc-create -n sip -f /etc/lxc/default.conf -t alpine}}
Create the startup Script
Create the startup Script
{{Cmd|ln -s /etc/init.d/lxc /etc/init.d/lxc.sip}}
{{Cmd|ln -s lxc /etc/init.d/lxc.sip}}


Edit the container's config file found at /var/lib/lxc/sip/config, to reflect the network for the sip container
Edit the container's config file found at /var/lib/lxc/sip/config, to reflect the network for the sip container
Line 815: Line 887:
{{Cmd|lxc-console -n sip}}
{{Cmd|lxc-console -n sip}}
Login as root
Login as root
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| k}}}}
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| q}}}}
Remove obsolete /etc/network/interfaces
Remove obsolete /etc/network/interfaces
{{Cmd|rm /etc/network/interfaces}}
{{Cmd|rm /etc/network/interfaces}}
Line 825: Line 897:
auto eth0
auto eth0
iface eth0 inet static
iface eth0 inet static
address <%SIP_IP_ADDRESS%>
address 10.2.0.4
netmask <%SIP_NETMASK%>
netmask 255.255.255.0
gateway <%DMVPN_SIP_IP_ADDRESS%>
gateway 10.2.0.1
}}
}}


Startup networking  
Startup networking  
{{Cmd| /etc/init.d/networking start}}
{{Cmd| rc-service networking start}}


Configure and enable proxy settings
Configure and enable proxy settings
{{Cmd|setup-proxy http://<%WEBPROXY_IP_ADDRESS%>:8080
{{Cmd|setup-proxy <nowiki>http://10.1.0.2:8080</nowiki>
. /etc/profile.d/proxy.sh}}
. /etc/profile.d/proxy.sh}}


Line 844: Line 916:


Start ssh
Start ssh
{{Cmd|/etc/init.d/sshd start}}
{{Cmd|rc-service sshd start}}


Configure a passwd for the container
Configure a passwd for the container
Line 851: Line 923:
Setup acf for web administration
Setup acf for web administration
{{Cmd|setup-acf}}
{{Cmd|setup-acf}}
==Setup Firewall==
 
Create the policies for the firewall
== Setup Firewall ==
{{Cmd|apk add acf-awall}}
 
With your favorite editor, create the policies for the firewall
{{cat|/etc/awall/optional/base.json|
{{cat|/etc/awall/optional/base.json|
{
{
Line 904: Line 979:
awall enable sip
awall enable sip
awall enable syslog
awall enable syslog
awall activate
awall activate -f
rc-update add iptables
rc-update add iptables
}}
}}
Line 913: Line 988:
apk add acf-postgresql}}
apk add acf-postgresql}}
Prepare the database
Prepare the database
{{Cmd|/etc/init.d/postgresql setup}}
{{Cmd|rc-service postgresql setup}}
Configure /var/lib/postgresql/9.3/data/postgresql.conf to set the 'listen_addresses', and the 'log_destination' variables to show:
Configure /var/lib/postgresql/9.3/data/postgresql.conf to set the 'log_destination' variable to show:
{{cat|/var/lib/postgresql/9.3/data/postresql.conf|
{{cat|/var/lib/postgresql/9.3/data/postresql.conf|
..
listen_addresses {{=}}'<%SIP_IP_ADDRESS%>
..
..
log_destination {{=}}'syslog'
log_destination {{=}}'syslog'
}}
}}
Start up the database and configure postgresql to start at boot up
Start up the database and configure postgresql to start at boot up
{{Cmd|/etc/init.d/postgresql start
{{Cmd|rc-service postgresql start
rc-update add postgresql}}
rc-update add postgresql}}
{{Todo| configure Kamalio}}
 
== Install acf-provisioning ==
* vi /etc/kamailio/kamctlrc
<pre>
SIP_DOMAIN=sip.office.example.net
DBENGINE=PGSQL
DBHOST=127.0.0.1
DBNAME=openser
DBRWUSER=openser
DBRWPW="openser"
DBROUSER=openserro
DBROPW=openserro
DBROOTUSER="postgres"
</pre>
* yes | kamdbctl create openser
* apk add acf-provisioning lua-socket lua-expat
* Create /etc/provisioning/update_device_params.lua:
<pre>
-- This is the script run after editing device params - basically only worried about extension and password
local functions, params, oldparams = ...
 
require("posix")
require("luasql.postgres")
 
local root = "/var/www/provisioning/htdocs/"
local b62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
 
APP.logevent("got to update_device_params script")
 
local function generatepw()
-- generate a random 12-character alphanumeric string
local file = io.open("/dev/urandom")
local str = ""
if file == nil then return nil end
local size = 12
while (size > 0 ) do
local offset = (string.byte(file:read(1)) % 62) + 1
str = str .. string.sub (b62, offset, offset)
size = size - 1
end
return str
end
 
local function findip(mac)
if not mac or mac == "" then
return nil
end
local ipaddr = functions.getselectresponse("SELECT ip FROM provisioning_requests WHERE mac~*'"..mac.."'")
if ipaddr and ipaddr[1] then
return ipaddr[1].ip
end
end
 
local function addfuturenotify(ipaddr, extension)
local res, err = pcall(function()
functions.runsqlcommand("DELETE FROM notify WHERE ipaddr='"..ipaddr.."' AND extension='"..extension.."'")
end)
if not res and err then
if string.match(err, "relation \"(%S+)\" does not exist") then
functions.runsqlcommand("CREATE TABLE notify (ipaddr text, extension text, seasoned boolean DEFAULT false)")
else
assert(res, err)
end
end
-- if table missing, create it and delete again
functions.runsqlcommand("INSERT INTO notify VALUES('"..ipaddr.."', '"..extension.."')")
end
 
local notify_device = function(mac, extension)
local ipaddr = findip(mac)
if ipaddr then
APP.logevent("Notifying "..ipaddr.." to update for "..(mac or ""))
os.execute("/etc/provisioning/notify_device "..ipaddr.." "..extension)
addfuturenotify(ipaddr, extension)
else
APP.logevent("Warning - could not find IP address for "..(mac or ""))
end
end
 
local kam = APP:new("kamailio/kamailio")
 
-- A table of devices to notify to update
local devices = {}
 
-- First, we check the registration numbers / passwords to 1) set a random password (if necessary) 2) make sure password matches any other registrations to same extension 3) push changes (add / delete / or update) to Kam database
-- Because this script also handles when device classes change, we have to consider that extensions are added / removed
local regs = {}
local forwarding = {"forwardnoanswerenable", "forwardnoanswer", "forwardbusyenable", "forwardbusy", "forwardallenable", "forwardall"}
local forwardsettings = {} -- Collect forwarding settings
local passwords = {} -- Collect old extension/password pairs
for name,val in pairs(params.value) do
if not regs[name] and string.match(name, "^reg") then
regs[name] = true
end
end
oldparams = oldparams or {value={}}
for name,val in pairs(oldparams.value) do
if string.match(name, "^reg") then
if val.value and val.value.extension and val.value.password then
passwords[val.value.extension.value] = val.value.password.value
end
if val.value and val.value.extension then
local fwd = {}
for i,f in ipairs(forwarding) do
if val.value[f] then
fwd[f] = val.value[f].value
end
end
forwardsettings[val.value.extension.value] = fwd
end
if not regs[name] then
regs[name] = true
end
end
 
end
local extension_id = params.value.reg1.value.extension.param_id
local password_id = params.value.reg1.value.password.param_id
functions.runsqlcommand("BEGIN TRANSACTION")
for name,val in pairs(regs) do
local new = ""
local old = ""
if params.value[name] then new = params.value[name].value.extension.value end
if oldparams.value[name] then old = oldparams.value[name].value.extension.value end
if new ~= old then
-- Extension changed
-- First, let's remove the stuff for the old extension
if old ~= "" then
local others = functions.getselectresponse("SELECT count(*) FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..old.."'")
if others[1].count == "0" then
APP.logevent("Removing old registration "..old)
-- remove the registration
kam.model.delete_user(old)
end
end
-- Now, add the new extension
if new ~= "" then
local pass
if params.value[name].value.password.value ~= "" and (not oldparams.value[name] or (oldparams.value[name].value.password.value ~= params.value[name].value.password.value)) then
-- The password parameter was changed and not blank, so use it
pass = params.value[name].value.password.value
APP.logevent("Added a new registration "..new.." with specified password "..pass)
-- There may be other devices with this extension
local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
for i,o in ipairs(others) do
-- We'll notify whether it changed or not
devices[o.value] = new
end
elseif passwords[new] then
pass = passwords[new]
APP.logevent("Added a new registration "..new.." with reused password "..pass)
else
local others = functions.getselectresponse("SELECT * FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."' AND device_id!='"..params.value.device_id.value.."'")
-- If this is a new registration, use a new password
if #others == 0 then
pass = generatepw()
APP.logevent("Added a new registration "..new.." with random password "..pass)
else
-- Use the old password
local p = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id='"..password_id.."' AND device_id='"..others[1].device_id.."' AND group_name='"..others[1].group_name.."' LIMIT 1")
pass = p[1].value
APP.logevent("Added a new registration "..new.." with reused password "..pass)
end
end
passwords[new] = pass
params.value[name].value.password.value = pass
functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..password_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..password_id.."', '"..pass.."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
local u = kam.model.get_user(new)
if u.value.username.errtxt then
-- Add this registration to Kam
local u = kam.model.get_new_user()
u.value.username.value = new
u.value.password.value = pass
u.value.password_confirm.value = pass
kam.model.create_new_user(u)
else
u.value.password.value = pass
u.value.password_confirm.value = pass
kam.model.update_user(u)
end
-- Let's also look at the forwarding settings
local change = false
local supported = false
local fwd = {}
for i,f in ipairs(forwarding) do
if params.value[name].value[f] then
supported = true
fwd[f] = params.value[name].value[f].value
if ((not oldparams.value[name] or not oldparams.value[name].value[f]) and (params.value[name].value[f].value ~= params.value[name].value[f].default)) or
(oldparams.value[name] and oldparams.value[name].value[f] and (params.value[name].value[f].value ~= oldparams.value[name].value[f].value)) then
change = true
end
end
if change then
local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
for i,o in ipairs(others) do
-- We'll notify whether it changed or not
devices[o.value] = new
end
end
end
if supported then
if not change and forwardsettings[new] then
fwd = forwardsettings[new]
elseif not change then
-- This is a new extension, and the forwarding has not been set. We should check to see if there are any other devices with this extension
local others = functions.getselectresponse("SELECT * FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."' AND device_id!='"..params.value.device_id.value.."'")
if #others > 0 then
-- Use the existing settings
for i,f in ipairs(forwarding) do
local v = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id='"..params.value[name].value[f].param_id.."' AND device_id='"..others[1].device_id.."' AND group_name='"..others[1].group_name.."' LIMIT 1")
if #v > 0 then
fwd[f] = v[1].value
else
fwd[f] = params.value[name].value[f].default
end
end
end
end
if not change then
for i,f in ipairs(forwarding) do
params.value[name].value[f].value = fwd[f]
end
end
forwardsettings[new] = fwd
for i,f in ipairs(forwarding) do
functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..params.value[name].value[f].param_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
if fwd[f] ~= params.value[name].value[f].default then
functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..params.value[name].value[f].param_id.."', '"..tostring(fwd[f]).."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
end
end
end
end
elseif params.value[name] and oldparams.value[name] and params.value[name].value.extension.value ~= "" then
if params.value[name].value.password.value ~= oldparams.value[name].value.password.value then
-- Password changed - make any other registrations to this extension also change
local pass = params.value[name].value.password.value
if pass and pass ~= "" then
APP.logevent("Password changed for "..new.." from "..oldparams.value[name].value.password.value.." to "..pass)
else
pass = generatepw()
APP.logevent("Password cleared for "..new..", so set new random password "..pass)
end
passwords[new] = pass
functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..password_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..password_id.."', '"..pass.."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
local u = kam.model.get_user(new)
u.value.password.value = params.value[name].value.password.value
u.value.password_confirm.value = params.value[name].value.password.value
kam.model.update_user(u)
-- Have to notify those other devices too
local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
for i,o in ipairs(others) do
devices[o.value] = new
end
end
local change = false
local fwd = {}
for i,f in ipairs(forwarding) do
if params.value[name].value[f] and (not oldparams.value[name].value[f] or params.value[name].value[f].value ~= oldparams.value[name].value[f].value) then
change = true
break
end
end
if change then
-- Forwarding settings changed - make any other registrations to this extension also change
for i,f in ipairs(forwarding) do
functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..params.value[name].value[f].param_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
if params.value[name].value[f].value ~= params.value[name].value[f].default then
functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..params.value[name].value[f].param_id.."', '"..tostring(params.value[name].value[f].value).."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
end
end
-- Have to notify those other devices too
local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
for i,o in ipairs(others) do
devices[o.value] = new
end
end
end
end
functions.runsqlcommand("COMMIT")
 
-- If reg1 or freepstn changed, then need to change free-pstn in kam
-- Since there can be multiple devices with the same reg1, we can't rely on params and oldparams
-- Check both reg's
--APP.logevent(session.serialize("params", params))
local reg = {}
if oldparams.value.reg1 and oldparams.value.reg1.value.extension and oldparams.value.reg1.value.extension.value ~= "" then
reg[oldparams.value.reg1.value.extension.value] = false
end
if params.value.reg1 and params.value.reg1.value.extension and params.value.reg1.value.extension.value ~= "" then
local pstn = false
if params.value.routing and params.value.routing.value.freepstn then
pstn = params.value.routing.value.freepstn.value
end
reg[params.value.reg1.value.extension.value] = pstn
end
 
APP.logevent("Looking at free-pstn")
for r,f in pairs(reg) do
-- if we're not sure, check provisioning database
if not f then
-- Most devices will have free-pstn due to class-of-service, but can be overridden, so this can get tricky
-- Check all devices where reg1 extension = r, and see what the freepstn value is for each
local others = functions.getselectresponse("SELECT CASE WHEN v.value IS NOT NULL THEN v.value WHEN g2p.value IS NOT NULL THEN g2p.value ELSE p.value END AS value "..
"FROM (devices_to_classes d2t JOIN provisioning_classes t USING(class_id) JOIN classes_to_param_groups t2g USING (class_id) JOIN provisioning_groups g USING(group_id) "..
"JOIN param_groups_to_params g2p USING(group_id) JOIN provisioning_params p USING(param_id)) LEFT JOIN provisioning_values v ON (d2t.device_id=v.device_id AND p.param_id=v.param_id AND g.name=v.group_name ) "..
"WHERE p.name='freepstn' AND d2t.device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND group_name='reg1' AND value='"..r.."')")
for i,o in ipairs(others) do
-- Now check the freepstn value for each one
if o.value == "true" then
f = true
break
end
end
end
-- Now, check the Kamailio group table
local alreadythere = false
local entries = kam.model.list_table_entries("grp")
for i,e in ipairs(entries.value.entries.value) do
if e.username == r then
alreadythere = true
if not f then
APP.logevent("Removing free-pstn for "..r)
-- Remove free-pstn from the old extension
kam.model.delete_table_entry("grp", e.id)
end
break
end
end
if f and not alreadythere then
APP.logevent("Adding free-pstn for "..r)
-- Add free-pstn to the new extension
local e = kam.model.get_table_entry("grp")
e.value.username.value = r
e.value.domain.value = ""
e.value.grp.value = "free-pstn"
e.value.last_modified.value = os.date("%c")
kam.model.create_table_entry(e)
end
end
 
kam:destroy()
 
-- If the mac address changed for Polycom with valid MAC (not blank or all 0's), we need to move the associated config files
if oldparams.value.device and oldparams.value.device.value.mac and oldparams.value.device.value.mac.value ~= "" and params.value.device and params.value.device.value.mac and params.value.device.value.mac.value ~= oldparams.value.device.value.mac.value then
if string.match(oldparams.value.device.label, "Polycom") and string.match(oldparams.value.device.value.mac.value, "[1-9A-F]") then
local deletefiles = true
if string.match(params.value.device.value.mac.value, "[1-9A-F]") then
--APP.logevent("Moving files for "..oldparams.value.device.value.mac.value)
deletefiles = false
else
--APP.logevent("Deleting files for "..oldparams.value.device.value.mac.value)
end
local path = root.."Polycom/"
if posix.stat(path, "type") == "directory" then
                for d in posix.files(path) do
                        if string.match(d, string.lower(oldparams.value.device.value.mac.value)) and posix.stat(path..d, "type") == "regular" then
                                local newfile = string.gsub(d, string.lower(oldparams.value.device.value.mac.value), string.lower(params.value.device.value.mac.value))
if deletefiles then
                                --APP.logevent("deleting "..path..d)
                                os.remove(path..d)
else
                                --APP.logevent("moving "..path..d.." to "..path..newfile)
                                os.rename(path..d, path..newfile)
end
end
                        end
                end
        end
end
 
-- Then, notify the phone to pull it's config
-- Try to get a valid extension currently on the device
local oldexten = ""
for name,val in pairs(oldparams.value) do
if string.match(name, "^reg") and val.value.extension and val.value.extension.value ~= "" then
oldexten = val.value.extension.value
break
end
end
if params.value.device and params.value.device.value.mac and params.value.device.value.mac.value ~= "" then
devices[params.value.device.value.mac.value] = oldexten
end
if oldparams.value.device and oldparams.value.device.value.mac and oldparams.value.device.value.mac.value ~= "" then
devices[oldparams.value.device.value.mac.value] = oldexten
end
 
for name,value in pairs(devices) do
notify_device(name,value)
end
</pre>
* Create /etc/provisioning/provisioning_db_script
<pre>
#!/usr/bin/lua
 
local path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin "
local creation_script = {
-- Parameters
"INSERT INTO provisioning_params VALUES(default, 'freepstn', 'boolean', 'Free PSTN Access', '', 'false', '200', '')",
 
-- Parameter Groups
"INSERT INTO provisioning_groups VALUES(default, 'routing', 'Free PSTN Access', '31')",
"INSERT INTO provisioning_groups VALUES(default, 'routing', 'No Free PSTN Access', '32')",
 
-- param_groups_to_params (group_id, param_id, value, editable)
"INSERT INTO param_groups_to_params VALUES((SELECT group_id FROM provisioning_groups WHERE label='Free PSTN Access'), (SELECT param_id FROM provisioning_params WHERE name='freepstn'), 'true', false)",
"INSERT INTO param_groups_to_params VALUES((SELECT group_id FROM provisioning_groups WHERE label='No Free PSTN Access'), (SELECT param_id FROM provisioning_params WHERE name='freepstn'), 'false', false)",
-- Classes
-- provisioning_class_groups (class_group_id, name, label, seq)
"INSERT INTO provisioning_class_groups VALUES(default, 'routing', 'Routing', '3')",
 
-- provisioning_classes (class_id, class_group_id, label, seq)
"INSERT INTO provisioning_classes VALUES(default, (SELECT class_group_id FROM provisioning_class_groups WHERE name='routing'), 'Free PSTN Access', '1')",
"INSERT INTO provisioning_classes VALUES(default, (SELECT class_group_id FROM provisioning_class_groups WHERE name='routing'), 'No Free PSTN Access', '2')",
 
-- classes_to_param_groups (class_id, group_id)
"INSERT INTO classes_to_param_groups VALUES((SELECT class_id FROM provisioning_classes WHERE label='Free PSTN Access'), (SELECT group_id FROM provisioning_groups WHERE label='Free PSTN Access'))",
"INSERT INTO classes_to_param_groups VALUES((SELECT class_id FROM provisioning_classes WHERE label='No Free PSTN Access'), (SELECT group_id FROM provisioning_groups WHERE label='No Free PSTN Access'))",
 
-- provisioning_options
"INSERT INTO provisioning_options VALUES((SELECT param_id FROM provisioning_params WHERE name='polycomringtone'), 'Warble', '15', '15')",
"INSERT INTO provisioning_options VALUES((SELECT param_id FROM provisioning_params WHERE name='polycomringtone'), 'Analog Ring', '16', '16')",
}
 
 
local f = io.popen("/usr/share/acf/www/cgi-bin/cli /provisioning/provisioning/getdevicevalues")
print(f:read("*a"))
f:close()
for i,c in ipairs(creation_script) do
print(path..'psql -U postgres -c "'..c..'" provisioning 2>&1')
local f = io.popen(path..'psql -U postgres -c "'..c..'" provisioning 2>&1')
print(f:read("*a"))
f:close()
end
</pre>
* chmod 755 /etc/provisioning/provisioning_db_script
* /etc/provisioning/provisioning_db_script
* echo '*      *      *      *      *      run-parts /etc/periodic/1min' >> /etc/crontabs/root
* mkdir /etc/periodic/1min/
* /etc/periodic/1min/notify_device
<pre>
#!/usr/bin/lua
 
-- Load libraries
require("luasql.postgres")
 
-- Set variables
local DatabaseName = "provisioning"
local DatabaseUser = "postgres"
local DatabasePassword
 
local path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin "
local env
local con
 
-- ################################################################################
-- LOCAL FUNCTIONS
local function assert (v, m)
if not v then
m = m or "Assertion failed!"
error(m, 0)
end
return v, m
end
 
-- Escape special characters in sql statements
local escape = function(sql)
sql = sql or ""
sql = string.gsub(sql, "'", "''")
return string.gsub(sql, "\\", "\\\\")
end
 
local databaseconnect = function()
if not con then
-- create environment object
env = assert (luasql.postgres())
-- connect to data source
local err
con, err = assert(env:connect(DatabaseName, DatabaseUser, DatabasePassword))
return true
end
return false
end
 
local databasedisconnect = function()
if env then
env:close()
env = nil
end
if con then
con:close()
con = nil
end
end
 
local getselectresponse = function(sql)
local retval = {}
local cur = assert (con:execute(sql))
local row = cur:fetch ({}, "a")
while row do
local tmp = {}
for name,val in pairs(row) do
tmp[name] = val
end
retval[#retval + 1] = tmp
row = cur:fetch (row, "a")
end
cur:close()
return retval
end
 
databaseconnect()
 
-- First, let's notify for the seasoned requests
local reqs = getselectresponse("SELECT * FROM notify WHERE seasoned='true'")
for i,r in ipairs(reqs) do
os.execute("/etc/provisioning/notify_device "..r.ipaddr.." "..r.extension)
end
 
-- Then mark any others as seasoned
assert(con:execute("DELETE FROM notify WHERE seasoned='true'"))
assert(con:execute("UPDATE notify SET seasoned='true' WHERE seasoned='false'"))
 
databasedisconnect()
</pre>
* apk add lighttpd
* rm /etc/lighttpd/lighttpd.conf
* ln -s /etc/provisioning/lighttpd.sample.conf /etc/lighttpd/lighttpd.conf
* /etc/lighttpd/mod_cgi.conf:
<pre>
###############################################################################
# mod_cgi.conf
# include'd by lighttpd.conf.
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/mod_cgi.conf,v 1.1 2005/08/27 12:36:13 ka0ttic Exp $
###############################################################################
 
#
# see cgi.txt for more information on using mod_cgi
#
 
server.modules += ("mod_cgi")
 
# NOTE: this requires mod_alias
alias.url = (
    "/cgi-bin/"     =>     var.basedir + "/cgi-bin/"
)
 
#
# Note that you'll also want to enable the
# cgi-bin alias via mod_alias (above).
#
 
$HTTP["url"] =~ "^/cgi-bin/" {
    # disable directory listings
    dir-listing.activate = "disable"
    # only allow cgi's in this directory
    cgi.assign = (
".pl" => "/usr/bin/perl",
".cgi" => "",
"" => ""
)
}
 
# vim: set ft=conf foldmethod=marker et :
</pre>
* rc-service lighttpd start
* rc-update add lighttpd
 
* Customize provisioning:
<pre>
Parameter Default Value
registrar IP address or host name of SIP Router(s)
digitmap Digit map for your phone system (See Polycom Digit Map Reference)
digitmaptimeout Timeout in seconds corresponding to digitmap
sntpserver 10.2.0.1 (DMVPN spoke node)
timezone Timezone information for this location - see below
musiconhold SIP uri of the music-on-hold service - moh@media.office.example.net
adminpassword Administration password for advanced settings on phone (on-screen and web interface)
</pre>
* apk fetch --stdout acf-provisioning-polycom | tar -C / -zx
* Create a test Polycom device and boot it in the voice network to verify that it works
 
== Install Kamailio ==
* apk add kamailio kamailio-presence kamailio-pcre kamailio-postgres
* rc-service kamailio start
* rc-update add kamailio
 
* FUTURE: POST CONFIG THAT USES SUBSCRIBER TABLE FOR AUTH
 
= Install the SIP Media container =
 
== Create and Configure the container ==
{{Cmd|lxc-create -n sipmedia -f /etc/lxc/default.conf -t alpine}}
Create the startup Script
{{Cmd|ln -s lxc /etc/init.d/lxc.sipmedia}}
 
Edit the container's config file found at /var/lib/lxc/sipmedia/config, to reflect the network for the SIP Media container
 
{{cat|/var/lib/lxc/sipmedia/config|
...
lxc.network.link {{=}} bond0.1101
...
}}
 
Start the container
{{Cmd|rc-service lxc.sipmedia}}
 
Configure the container to automatically start
{{Cmd|rc-update add lxc.sipmedia}}
 
== Enter the SIP Media container ==
{{Cmd|lxc-console -n sipmedia}}
Login as root
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| q}}}}
Remove obsolete /etc/network/interfaces
{{Cmd|rm /etc/network/interfaces}}
Create and configure the new /etc/network/interfaces as shown below:
{{cat|/etc/network/interfaces|
auto lo
iface lo inet loopback
 
auto eth0
iface eth0 inet static
address 10.2.0.5
netmask 255.255.255.0
gateway 10.2.0.1
}}
 
Startup networking
{{Cmd| rc-service networking start}}
 
Configure and enable proxy settings
{{Cmd|setup-proxy <nowiki>http://10.1.0.2:8080</nowiki>
. /etc/profile.d/proxy.sh}}
 
Configure remote administration
{{Cmd|apk update
setup-sshd -c openssh
sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config
sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config}}
 
Start ssh
{{Cmd|rc-service sshd start}}
 
Configure a passwd for the container
{{Cmd|passwd}}
 
Setup acf for web administration
{{Cmd|setup-acf}}
 
== Setup Firewall ==
{{Cmd|apk add acf-awall}}
 
With your favorite editor, create the policies for the firewall
{{cat|/etc/awall/optional/base.json|
{
  "description": "Management",
 
  "policy": [
    { "in": "_fw", "action": "accept" }
  ],
 
  "filter": [
    {
      "out": "_fw",
      "service": [ "ssh", "https", "ping" ],
      "action": "accept"
    }
  ]
}
}}
{{cat|/etc/awall/optional/sip-track.json|
{
 
"description": "Phone system with SIP connection tracking",
 
"filter": [
{
      "out": "_fw",
      "service": [ "sip", "sip-tls" ],
      "action": "accept"
    }
]
 
}
}}
Enable and activate firewall policies, and configure iptables to start at boot
{{Cmd|awall enable base
awall enable sip-track
awall activate -f
rc-update add iptables
}}
 
== Install and Configure Freeswitch ==
Install package
{{Cmd|Install Freeswitch Package}}
 
Configure /etc/freeswitch/freeswitch.xml
{{cat|/etc/freeswitch/freeswitch.xml|
<pre>
...
    <extension name="hold_music">                                                                                                                                                                             
      <condition field="destination_number" expression="^moh$">                                                                                                                                               
        <action application="answer"/>                                                                                                                                                                       
        <action application="playback" data="$${hold_music}"/>                                                                                                                                               
      </condition>                                                                                                                                                                                           
    </extension>
...
<X-PRE-PROCESS cmd="set" data="hold_music=local_stream://default"/>
...
FUTURE: ADD VOICEMAIL
</pre>
}}
Start Freeswitch and configure to start at boot
{{Cmd|rc-service freeswitch start
rc-update add freeswitch}}
 
= Install the WiFi Web Proxy Container =
 
== Create and Configure the container ==
{{Cmd|lxc-create -n wifi -f /etc/lxc/default.conf -t alpine}}
Create the startup Script
{{Cmd|ln -s lxc /etc/init.d/lxc.wifi}}
 
Edit the container's config file found at /var/lib/lxc/wifi/config, to reflect the network for the wifi container
 
{{cat|/var/lib/lxc/wifi/config|
...
lxc.network.link {{=}} bond0.701
...
}}
 
Start the container
{{Cmd|/etc/iniit.d/lxc.wifi}}
 
Configure the container to automatically start
{{Cmd|rc-update add lxc.wifi}}
 
== Enter the wifi container ==
{{Cmd|lxc-console -n wifi}}
Login as root
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| q}}}}
Remove obsolete /etc/network/interfaces
{{Cmd|rm /etc/network/interfaces}}
Create and configure the new /etc/network/interfaces as shown below:
{{cat|/etc/network/interfaces|
auto lo
iface lo inet loopback
 
auto eth0
iface eth0 inet static
address 172.17.48.1
netmask 255.255.255.0
 
auto eth1
iface eth1 inet static
address 10.1.0.254
netmask 255.255.255.252
gateway 10.1.0.253
 
auto eth2
iface eth2 inet static
address 10.1.0.131
netmask 255.255.255.192
}}
 
Startup networking
{{Cmd| rc-service networking start}}
 
 
Configure remote administration
{{Cmd|apk update
setup-sshd -c openssh
sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config
sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config}}
 
Start ssh
{{Cmd|rc-service sshd start}}
 
Configure a passwd for the container
{{Cmd|passwd}}
 
Setup acf for web administration
{{Cmd|setup-acf}}
 
== Setup Firewall ==
{{Cmd|apk add acf-awall}}
{{Todo|Need to lock down firewall rules}}
 
==Install and Configure the Recursive DNS Service ==
Install unbound package
{{Cmd|apk add unbound}}
With your favorite editor configure /etc/unbound/unbound.conf
{{cat|/etc/unbound/unobund.conf|
server:
        verbosity: 1
        interface: 172.17.48.1
        do-ip4: yes
        do-ip6: no
        do-udp: yes
        do-tcp: yes
        do-daemonize: yes
        access-control: 172.17.0.0/16 allow
        access-control: 127.0.0.0/8 allow
 
do-not-query-localhost: no
 
root-hints: "/etc/unbound/root.hints"
 
python:
remote-control:
        control-enable: no
}}
== Install and Configure the Proxy service ==
Install the necessary packages
{{Cmd|apk add squid squark lighttpd}}
With your preferred editor configure /etc/squid/squid.conf
{{cat|/etc/squid/squid.conf|
<pre>
#Squid config
 
# This port listens for client requests
http_port 172.17.48.1:8080 transparent
http_port 127.0.0.1:8081
 
visible_hostname wifi.local
cache_mem 8 MB
# If you don't have an HD installed comment the "cache_dir" line below
cache_dir aufs /var/cache/squid 900 16 256
 
# Even though we only use one proxy, this line is recommended
# More info: http://www.squid-cache.org/Versions/v2/2.7/cfgman/hierarchy_stoplist.html
hierarchy_stoplist cgi-bin ?
 
# Keep 7 days of access logs
logfile_rotate 7
 
logformat squark %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %un %Sh/%<A %mt %rG
access_log /var/log/squid/access.log squark
cache_store_log none
pid_filename /var/run/squid.pid
 
# Make sure client IP is passed to Squark
log_uses_indirect_client on
acl_uses_indirect_client on
 
# Debugging Squid, see http://wiki.squid-cache.org/KnowledgeBase/DebugSections
# for more info
# Keep 7 days of cache log
debug_options rotate=7
 
# Web auditors want to see the full uri, even with the query terms
strip_query_terms off
 
refresh_pattern ^ftp: 1440 20% 10080
refresh_pattern ^gopher: 1440 0% 1440
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
refresh_pattern . 0 20% 4320
 
coredump_dir /var/cache/squid
 
dns_nameservers 172.17.48.1
 
#
# Authentication
#
# Squark external acl
#external_acl_type squark_snmp_auth_D children-max=1 ttl=4 grace=1 negative_ttl=0 concurrency=128 %SRC /usr/bin/squark-auth-snmp -c public -R <SWITCH_IP> -i <D_VLAN_IF> -v <D_VLAN_ID> -f "%N-%i=%I" -T /etc/squark/topology.conf
 
#
# Access Control Lists (ACL's)
#
 
# Standard ACL settings
acl QUERY urlpath_regex cgi-bin \? asp aspx jsp
acl to_localhost dst 172.17.48.1
acl SSL_ports port 443 563 8004 9000
acl Safe_ports port 21 70 80 81 210 280 443 563 499 591 777 1024 1022 1025-65535
acl purge method PURGE
acl CONNECT method CONNECT
 
#acl SquarkAuth external squark_auth
#acl SquarkSnmpAuthD external squark_snmp_auth_D
 
# Squark filter
url_rewrite_program /usr/bin/squark-filter
url_rewrite_children 1 concurrency=128
 
# Require authentication
acl userlist  src all
 
# Definition of zones
acl Zone_D src 172.17.48.0/24
 
 
#
# Access restrictions
#
 
cache deny QUERY
 
# Only allow cachemgr access from localhost
http_access allow manager localhost
http_access deny manager
 
# Only allow purge requests from localhost
http_access allow purge localhost
http_access deny purge
 
# Deny requests to unknown ports
http_access deny !Safe_ports
 
# Deny CONNECT to other than SSL ports
http_access deny CONNECT !SSL_ports
 
# Allow hosts in Zone_D to access the entire Internet
http_access allow Zone_D
 
# Denying all access not explictly allowed
http_access deny all
 
##Squark URL rewriter
#Prevent squark from filtering itself
url_rewrite_access deny manager
url_rewrite_access deny to_localhost
 
#Finally, permit access
url_rewrite_access allow Zone_D
 
http_reply_access allow all
icp_access allow all
</pre>
}}
Configure lighttpd
{{cat|/etc/lighttpd/lighttpd.conf|
<pre>
var.basedir  = "/var/www/localhost"
var.logdir  = "/var/log/lighttpd"
var.statedir = "/var/lib/lighttpd"
 
server.modules = (
    "mod_access",
    "mod_accesslog",
    "mod_extforward"
)
 
include "mime-types.conf"
include "mod_cgi.conf"
 
server.username      = "lighttpd"
server.groupname    = "lighttpd"
 
server.document-root = var.basedir + "/squark"
server.pid-file      = "/var/run/lighttpd.pid"
 
server.errorlog      = var.logdir  + "/error.log"
 
server.indexfiles    = ("index.php", "index.html",
"index.htm", "default.htm")
 
 
server.follow-symlink = "enable"
 
server.port          = 81
server.bind          = "172.17.48.1"
 
static-file.exclude-extensions = (".php", ".pl", ".cgi", ".fcgi")
 
accesslog.filename  = var.logdir + "/access.log"
 
url.access-deny = ("~", ".inc")
 
extforward.forwarder = ("172.17.48.1" => "trust")
</pre>
}}
{{cat|/etc/lighttpd/mod_cgi.conf|
<pre>
###############################################################################
# mod_cgi.conf
# include'd by lighttpd.conf.
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/mod_cgi.conf,v 1.1 2005/08/27 12:36:13 ka0ttic Exp $
###############################################################################
 
#
# see cgi.txt for more information on using mod_cgi
#
 
server.modules += ("mod_cgi")
 
# NOTE: this requires mod_alias
alias.url = (
    "/cgi-bin/"     =>     var.basedir + "/cgi-bin/"
)
 
#
# Note that you'll also want to enable the
# cgi-bin alias via mod_alias (above).
#
 
$HTTP["url"] =~ "^/cgi-bin/" {
    # disable directory listings
    dir-listing.activate = "disable"
    # only allow cgi's in this directory
    cgi.assign = (
".pl" => "/usr/bin/perl",
".cgi" => "/usr/bin/haserl"
)
}
 
# vim: set ft=conf foldmethod=marker et :
</pre>
}}
Link Squark web pages to the Web server home directory
{{Cmd|ln -s /usr/share/squark/www/ /var/www/localhost/squark}}
Make 'squid' and 'lighttpd' users member of the group squark
{{Cmd|addgroup squid squark
addgroup lighttpd squark}}
Start lighttpd and configure the Web service to start at boot
{{Cmd|rc-service lighttpd start
rc-update add lighttpd}}
Start Squid and configure it to start at boot
{{Cmd|rc-service squid start
rc-update add squid}}
 
= See also =
* [[Freeswitch Voicemail On Alpine Linux]]
 
[[Category:ACF]]

Latest revision as of 10:48, 17 November 2023

Abstract: This document will outline how to provide various network services for a small remote office, using Linux containerization (LXC). It is designed to be a complement to the DMVPN spoke node.

The following services will be available in addition to the encrypted communications between offices provided by the DMVPN network:

  • Internet browsing proxy server with domain filtering (wired clients on protected internal network)
  • Separate proxy for wifi clients
  • SIP phone system including web based provisioning and basic voicemail services

The assumption is made that the following VLANs and subnets are used as the DMVPN document did:

Interface Description Subnet
bond0.3 Management 10.1.0.129/26
bond0.101 LAN 10.1.0.0/25
bond0.256 Internet from ISP1 Allocated from ISP
bond0.257 Internet from ISP2 Allocated from ISP
bond0.620 Transit between wifi proxy and dmvpn spoke node 10.1.0.252/30
bond0.701 WiFi clients (no access to DMVPN network) 172.17.48.0/24
bond0.1101 Voice 10.2.0.0/24


Tip: At the time of writing, the recommended Alpine version for building the Host box for the containers should be at minimum 2.7.9 64 bit.

Hardware

For an office that will serve under 20 people, the following containers can easily run on low-power hardware such as a Via Nano 1.6Ghz Jetway board with 8GB RAM with dual 500GB SATA hard drives running in RAID 1 (software).

Setup LXC Host Box

Boot Alpine USB

Follow the instructions at Create a Bootable Device.

Alpine Setup

setup-alpine

You will be prompted something like this... Suggestion on what you could enter...
Select keyboard layout [none]: Type an appropriate layout for you
Select variant: Type an appropriate layout for you (if prompted)
Enter system hostname (short form, e.g. 'foo') [localhost]: Enter the hostname, e.g. lxc-host
Available interfaces are: eth0
Enter '?' for help on bridges, bonding and vlans.
Which one do you want to initialize? (or '?' done')
Enter bond0.3
Available bond slaves are: eth0 eth1
Which slave(s) do you want to add to bond0? (or 'done') [eth0]
eth0 eth1
IP address for bond0? (or 'dhcp', 'none', '?') [dhcp]: Press Enter confirming 'none'
IP address for bond0.3? (or 'dhcp', 'none', '?') [dhcp]: <%LXCHOST_MANAGEMENT_IP_ADDRESS%>
Netmask? [255.255.255.0]: <%DMVPN_MANAGEMENT_NETMASK%>
Gateway? (or 'none') [none]: <%DMVPN_MANAGEMENT_NET_IP%>
Do you want to do any manual network configuration? [no] no
DNS domain name? (e.g. 'bar.com') []: Enter the domain name of your intranet, e.g., office.example.net
DNS nameservers(s)? []: 8.8.8.8 8.8.4.4 (we will change them later)
Changing password for root
New password:
Enter a secure password for the console
Retype password: Retype the above password
Which timezone are you in? ('?' for list) [UTC]: Press Enter confirming 'UTC'
HTTP/FTP proxy URL? (e.g. 'http://proxy:8080', or 'none') [none] http://<%DMVPN_LAN_IP%>:8080
Enter mirror number (1-9) or URL to add (or r/f/e/done) [f]: Select a mirror close to you and press Enter
Which SSH server? ('openssh', 'dropbear' or 'none') [openssh]: Press Enter confirming 'openssh'
Which NTP client to run? ('openntpd', 'chrony' or 'none') [chrony]: Press Enter confirming 'chrony'
Which disk(s) would you like to use? (or '?' for help or 'none') [none]: sda sdb
How would you like to use them? ('sys', 'data' or '?' for help): data
Enter where to store configs ('floppy', 'usb' or 'none') [usb]: Press Enter confirming 'usb'
Enter apk cache directory (or '?' or 'none') [/media/usb/cache]: Press Enter confirming '/media/usb/cache'

Upgrade packages

apk update apk upgrade

Save Changes

lbu commit

Finish Setup with a reboot

reboot

Setup Networking

With your favorite editor configure /etc/network/interfaces

Contents of /etc/network/interfaces

auto lo iface lo inet loopback auto bond0 iface bond0 inet manual bond-slaves eth0 eth1 bond-mode balance-tlb bond-miimon 100 bond-updelay 500 up ip link set $IFACE up down ip link set $IFACE down auto bond0.3 iface bond0.3 inet static address <%LXCHOST_MANAGEMENT_IP_ADDRESS%> netmask <%DMVPN_MANAGEMENT_NETMASK%> gateway <%DMVPN_MANAGEMENT_IP%> auto bond0.101 iface bond0.101 inet manual up ip link set $IFACE up down ip link set $IFACE down auto bond0.1101 iface bond0.1101 inet manual up ip link set $IFACE up down ip link set $IFACE down auto bond0.701 iface bond0.701 inet manual up ip link set $IFACE up down ip link set $IFACE down

Apply changes by restarting networking

rc-service networking restart

Enable IP Forwarding

echo "1" > /proc/sys/net/ipv4/ip_forward

Setup Firewall

apk add acf-awall

With your favorite editor, create the base policy for the firewall

Contents of /etc/awall/optional/base.json

{ "description": "Management", "policy": [ { "in": "_fw", "action": "accept" } ], "filter": [ { "out": "_fw", "service": [ "ssh", "https", "ping" ], "action": "accept" } ] }

Activate the firewall, and allow iptables to startup automatically at boot

modprobe ip_tables awall enable base awall activate -f rc-update add iptables

Install LXC

Install the LXC and Bridge packages

apk add lxc bridge

With your favorite editor configure /etc/lxc/default.conf

Contents of /etc/lxc/default.conf

## Allow containers in the same VLAN to see each other lxc.network.type = macvlan lxc.network.macvlan.mode = bridge lxc.network.link = bond0.3 lxc.network.name = eth0 ## Restrict capabilities of the containers lxc.cap.drop = sys_admin audit_control audit_write fsetid ipc_lock lxc.cap.drop = ipc_owner lease linux_immutable mac_admin mac_override lxc.cap.drop = mknod setfcap setpcap sys_module sys_nice sys_pacct lxc.cap.drop = sys_ptrace sys_rawio sys_tty_config sys_time

Finish Installation

lbu ci reboot

Install the Web Proxy Container

Create and Configure the container

lxc-create -n webproxy -f /etc/lxc/default.conf -t alpine

Create the startup Script

ln -s lxc /etc/init.d/lxc.webproxy

Edit the container's config file found at /var/lib/lxc/webproxy/config, to reflect the network for the web proxy container

Contents of /var/lib/lxc/webproxy/config

... lxc.network.link = bond0.101 ...

Start the container

rc-service lxc.webproxy start

Configure the container to automatically start

rc-update add lxc.webproxy

Enter the webproxy container

lxc-console -n webproxy

Login as root

Note: If the need arises to exit the container press Ctrl+ a + q

Remove obsolete /etc/network/interfaces

rm /etc/network/interfaces

Create and configure the new /etc/network/interfaces as shown below:

Contents of /etc/network/interfaces

auto lo iface lo inet loopback auto eth0 iface eth0 inet static address 10.1.0.2 netmask 255.255.255.192 gateway 10.1.0.1

Startup networking

rc-service networking start

Add rule to DMVPN awall policy to allow this proxy out to the internet

Note: this is to be configured on the DMVPN awall config

Contents of /etc/awall/optional/internet-host.json

{ "in": "B", "src": "$10.1.0.2", "out": "E", "action": "accept", },

Configure remote administration

apk update setup-sshd -c openssh sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config

Start ssh

rc-service sshd start

Configure a passwd for the container

passwd

Setup acf for web administration

setup-acf

Setup Firewall

apk add acf-awall

With your favorite editor, create the policies for the firewall

Contents of /etc/awall/optional/base.json

{ "description": "Management", "policy": [ { "in": "_fw", "action": "accept" } ], "filter": [ { "out": "_fw", "service": [ "ssh", "https", "ping" ], "action": "accept" } ] }

Contents of /etc/awall/optional/webproxy.json

{ "description": "Web Proxy", "filter": [ { "out": "_fw", "service": [ "http", "http-alt" ], "action": "accept" } ] }

Activate the firewall, and allow iptables to startup automatically at boot

awall enable base awall enable webproxy awall activate -f rc-update add iptables

Install and Configure the Squid Web Proxy Service

Install the required packages

apk add acf-squid squark acf-lighttpd

Configure /etc/squid/squid.conf, replace <%WEBPROXY_IP_ADDRESS%>, <%HOSTNAME%>, and <%DOMAIN%>

Contents of /etc/squid/squid.conf

#Squid config for webproxy

# This port listens for client requests
http_port 8080

visible_hostname <%HOSTNAME%>.<%DOMAIN%>
cache_mem 8 MB
# If you don't have an HD installed comment the "cache_dir" line below
cache_dir aufs /var/cache/squid 900 16 256

# Even though we only use one proxy, this line is recommended
# More info: http://www.squid-cache.org/Versions/v2/2.7/cfgman/hierarchy_stoplist.html
hierarchy_stoplist cgi-bin ?

# Keep 7 days of access logs
logfile_rotate 7

logformat squark %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %un %Sh/%<A %mt %rG
access_log /var/log/squid/access.log squark
cache_store_log none
pid_filename /var/run/squid.pid

# Make sure client IP is passed to Squark
log_uses_indirect_client on
acl_uses_indirect_client on

# Fix for problems with branch file transfer application
# ignore_expect_100 on (deprecated)

# Debugging Squid, see http://wiki.squid-cache.org/KnowledgeBase/DebugSections
# for more info
# Keep 7 days of cache log
debug_options rotate=7

# Web auditors want to see the full uri, even with the query terms
strip_query_terms off

refresh_pattern ^ftp:		1440	20%	10080
refresh_pattern ^gopher:	1440	0%	1440
refresh_pattern -i (/cgi-bin/|\?) 0	0%	0
refresh_pattern .		0	20%	4320

coredump_dir /var/cache/squid

# 
# Authentication
#


#
# Access Control Lists (ACL's)
#

# Standard ACL settings
acl QUERY urlpath_regex cgi-bin \? asp aspx jsp
acl to_localhost dst <%WEBPROXY_IP_ADDRESS%>
acl SSL_ports port 443 563 8004 9000
acl Safe_ports port 21 70 80 81 210 280 443 563 499 591 777 1024 1022 1025-65535
acl purge method PURGE
acl CONNECT method CONNECT

# Squark filter
url_rewrite_program /usr/bin/squark-filter
url_rewrite_children 1 concurrency=128

# Require authentication
acl userlist  src all

# Definition of zones 
acl Zone_B src <%LAN_SUBNET%>/<%LAN_SLASH_NOTATION%>
#acl Zone_D src <%WiFi_SUBNET%>/<%WiFi_SLASH_NOTATION%>

# Settings migrated from smn
acl Zone_B_AllowedUserDomains     dstdomain "/etc/squid/alloweduserdomains"
acl Zone_B_AllowedServicesHosts   src "/etc/squid/allowedserviceshosts"
acl Zone_B_AllowedServicesDomains dstdomain "/etc/squid/allowedservicesdomains"

# Settings migrated from services
acl AnonBrowsers browser "/etc/squid/anonbrowserlist"
acl AnonIPAddrs src "/etc/squid/anoniplist"
acl AnonDomain url_regex "/etc/squid/anondomainlist"

#
# Access restrictions
#

cache deny QUERY

# Only allow cachemgr access from localhost
http_access allow manager localhost
http_access deny manager

# Only allow purge requests from localhost
http_access allow purge localhost
http_access deny purge

# Deny requests to unknown ports
http_access deny !Safe_ports

# Deny CONNECT to other than SSL ports
http_access deny CONNECT !SSL_ports

# Allow hosts in Zone_B and Zone_C to access hosts listed in
# /etc/squid/alloweduserdomains
http_access allow Zone_B Zone_B_AllowedUserDomains

# Allow hosts listed in /etc/squid/allowedserviceshosts to
# access domains listed in /etc/squid/allowedservicesdomains
http_access allow Zone_B_AllowedServicesHosts Zone_B_AllowedServicesDomains


# Denying all access not explictly allowed
http_access deny all

##Squark URL rewriter
#Prevent squark from filtering itself
url_rewrite_access deny manager
url_rewrite_access deny to_localhost

#We do not want authentication for these sites:
url_rewrite_access deny Zone_B Zone_B_AllowedUserDomains
url_rewrite_access deny Zone_B Zone_B_AllowedServicesDomains

http_reply_access allow all
icp_access allow all

Configure /etc/lighttpd/lighttpd.conf, replace <%WEBPROXY_IP_ADDRESS%>

Contents of /etc/lighttpd/lighttpd.conf

##############################################################################
# Default lighttpd.conf for Gentoo.
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/lighttpd.conf,v 1.3 2005/09/01 14:22:35 ka0ttic Exp $
###############################################################################
var.basedir  = "/var/www/localhost"
var.logdir   = "/var/log/lighttpd"
var.statedir = "/var/lib/lighttpd"

server.modules = (
    "mod_access",
    "mod_accesslog",
    "mod_extforward"
)
include "mime-types.conf" 

include "mod_cgi.conf"

server.username      = "lighttpd"

server.groupname     = "lighttpd"

server.document-root = var.basedir + "/squark"

server.pid-file      = "/var/run/lighttpd.pid"
    
server.errorlog      = var.logdir  + "/error.log"

server.indexfiles    = ("index.php", "index.html",
                                                "index.htm", "default.htm")
server.follow-symlink = "enable"

static-file.exclude-extensions = (".php", ".pl", ".cgi", ".fcgi")

accesslog.filename   = var.logdir + "/access.log"

url.access-deny = ("~", ".inc")

extforward.forwarder = ("<%WEBPROXY_IP_ADDRESS%>" => "trust")

Configure mod_cgi.conf

Contents of /etc/lighttpd/mod_cgi.conf

###############################################################################
# mod_cgi.conf
# include'd by lighttpd.conf.
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/mod_cgi.conf,v 1.1 2005/08/27 12:36:13 ka0ttic Exp $
###############################################################################

#
# see cgi.txt for more information on using mod_cgi
#

server.modules += ("mod_cgi")

# NOTE: this requires mod_alias
alias.url = (
     "/cgi-bin/"	    =>	    var.basedir + "/cgi-bin/"
)

#
# Note that you'll also want to enable the
# cgi-bin alias via mod_alias (above).
#

$HTTP["url"] =~ "^/cgi-bin/" {
    # disable directory listings
    dir-listing.activate = "disable"
    # only allow cgi's in this directory
    cgi.assign = (
		".pl"	=>	"/usr/bin/perl",
		".cgi"	=>	"/usr/bin/haserl"
	)
}

Link the Squark web pages to the Web server home directory

ln -s /usr/share/squark/www/ /var/www/localhost/squark

Create a Squark group

addgroup squark

Make 'squid' and 'lighttpd' users member of the group squark

addgroup squid squark addgroup lighttpd squark

Start lighttpd, and configure the service to start on when container is booted

rc-service lighttpd start rc-update add lighttpd


Start Squid, and configure to start at boot

rc-service squid start rc-update add squid

Install the DHCP and DNS server Container

Create and Configure the container

lxc-create -n dhcpdns -f /etc/lxc/default.conf -t alpine

Create the startup Script

ln -s lxc /etc/init.d/lxc.dhcpdns

Edit the container's config file found at /var/lib/lxc/dhcpdns/config, to reflect the network for the web proxy container

Contents of /var/lib/lxc/dhcpdns/config

#Management Network Config
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.link = bond0.3
lxc.network.name = eth0

#WiFi Network Config
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.link = bond0.701
lxc.network.name = eth1

#Voice Network Config
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.link = bond0.1101
lxc.network.name = eth2

Start the container

rc-service lxc.dhcpdns start

Configure the container to automatically start

rc-update add lxc.dhcpdns

Enter the dhcpdns container

lxc-console -n dhcpdns

Login as root

Note: If the need arises to exit the container press Ctrl+ a + q

Remove obsolete /etc/network/interfaces

rm /etc/network/interfaces

Create and configure the new /etc/network/interfaces as shown below:

Contents of /etc/network/interfaces

auto lo iface lo inet loopback #Management VLAN auto eth0 iface eth0 inet static address 10.1.0.130 netmask 255.255.255.192 #WiFi VLAN auto eth1 iface eth1 inet static address 172.16.48.2 netmask 255.255.255.0 #Voice VLAN auto eth2 iface eth2 inet static address 10.2.0.2 netmask 255.255.255.0 gateway 10.2.0.1 up ip address add 10.2.0.3/24 dev eth0

Startup networking

rc-service networking start

Configure and enable proxy settings

setup-proxy http://10.1.0.2:8080 . /etc/profile.d/proxy.sh

Configure remote administration

apk update setup-sshd -c openssh sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config

Start ssh

rc-service sshd start

Configure a passwd for the container

passwd

Setup acf for web administration

setup-acf

Setup Firewall

apk add acf-awall

With your favorite editor, create the policies for the firewall

Contents of /etc/awall/optional/base.json

{ "description": "Management", "policy": [ { "in": "_fw", "action": "accept" } ], "filter": [ { "out": "_fw", "service": [ "ssh", "https", "ping" ], "action": "accept" } ] }

Contents of /etc/awall/optional/dhcp.json

{ "description": "DHCP", "filter": [ { "out": "_fw", "service": "dhcp", "action": "accept" } ] }

Contents of /etc/awall/optional/dns.json

{ "description": "DNS", "filter": [ { "out": "_fw", "service": "dns", "action": "accept" } ] }

Activate the firewall, and allow iptables to startup automatically at boot

awall enable base awall enable dhcp awall enable dns awall activate -f rc-update add iptables

Install and Configure DHCP and DNS services

install the dhcpd package

apk add acf-dhcp

Create a new dhcpd.conf file

Contents of /etc/dhcp/dhcpd.conf

## Common settings
default-lease-time 302400;
max-lease-time 604800;
ddns-update-style none;
log-facility local7;
authoritative;

## Common options
option time-servers 10.2.0.1;
option boot-server code 66 = string;

## Voice
subnet 10.2.0.0 netmask 255.255.255.0
{
   range "10.2.0.20 10.2.0.250";
   option domain-name-servers 10.2.0.2;
   option routers 10.2.0.1;
   option boot-server "http://10.2.0.4";
   option domain-name "office.example.net";
}

## WiFi
subnet 172.17.48.0 netmask 255.255.255.0
{
  range "172.17.48.10 172.17.48.250";
  option routers 172.17.48.1;
  option domain-name-servers 172.17.48.1;  
}

Start DHCP service and add to runlevel default

rc-service dhcpd start rc-update add dhcpd

Install nsd and unbound packages

apk add unbound

Remove unbound.conf

rm /etc/unbound/unbound.conf

Create with your favorite editor a new configuration for unbound

Contents of /etc/unbound/unbound.conf

#Recursive DNS configuration server: interface: 10.2.0.2 do-not-query-localhost: no verbosity: 1 do-ip4: yes do-ip6: no do-udp: yes do-tcp: yes do-daemonize: yes access-control: 10.1.0.0/16 allow access-control: 127.0.0.0/8 allow #use the root.hints file to determine where to send DNS queries outside of network root-hints: "/etc/unbound/root.hints" stub-zone: name: "office.example.net" stub-addr: 10.2.0.3 stub-zone: name: "example.net" stub-addr: 172.16.255.1 stub-addr: 172.16.255.2 stub-addr: 172.16.255.3 stub-addr: 172.16.255.4 stub-addr: 172.16.255.5 stub-addr: 172.16.255.7 stub-zone: name: "example2.net" stub-addr: 172.16.255.1 stub-addr: 172.16.255.2 stub-addr: 172.16.255.3 stub-addr: 172.16.255.4 stub-addr: 172.16.255.5 stub-addr: 172.16.255.7

Start Unbound and allow the container to use it {{Cmd|rc-service unbound start rc-update add unbound echo nameserver 10.2.0.2 > /etc/resolv.conf

Install nsd

apk add nsd

Configure nsd configuration

Contents of /etc/nsd/nsd.conf

server: ip-address: 10.2.0.3 port: 53 server-count: 1 ip4-only: yes hide-version: yes identity: "" zonesdir: "/etc/nsd" zone: name: office.example.net zonefile: office.example.net.zone

Configure Zone file for nsd

Contents of /etc/nsd/nsd.conf

$ORIGIN office.example.net. $TTL 86400 @ IN SOA ns admin ( 2013032200 ; Serial number [yyyymmddnn] 28800 ; Refresh 7200 ; Retry 864000 ; Expire 86400 ; Min TTL ) @ NS ns1 ; NSA Servers ns1 IN A 10.2.0.3 ;A Records for SIP Devices sip IN A 10.2.0.4 media IN A 10.2.0.5 ;NAPTR Records sip IN NAPTR 10 1 "s" "SIP+D2U" "" _sip._udp.sip.office.example.net. ;SIP SRV Record _sip._udp.sip IN SRV 10 100 5060 sip

Check nsd configuration and start service

nsd-checkconf /etc/nsd/nsd.conf rc-service nsd start rc-update add nsd

Install the SIP Container

Create and Configure the container

lxc-create -n sip -f /etc/lxc/default.conf -t alpine

Create the startup Script

ln -s lxc /etc/init.d/lxc.sip

Edit the container's config file found at /var/lib/lxc/sip/config, to reflect the network for the sip container

Contents of /var/lib/lxc/sip/config

... lxc.network.link = bond0.1101 ...

Start the container

/etc/iniit.d/lxc.sip

Configure the container to automatically start

rc-update add lxc.sip

Enter the sip container

lxc-console -n sip

Login as root

Note: If the need arises to exit the container press Ctrl+ a + q

Remove obsolete /etc/network/interfaces

rm /etc/network/interfaces

Create and configure the new /etc/network/interfaces as shown below:

Contents of /etc/network/interfaces

auto lo iface lo inet loopback auto eth0 iface eth0 inet static address 10.2.0.4 netmask 255.255.255.0 gateway 10.2.0.1

Startup networking

rc-service networking start

Configure and enable proxy settings

setup-proxy http://10.1.0.2:8080 . /etc/profile.d/proxy.sh

Configure remote administration

apk update setup-sshd -c openssh sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config

Start ssh

rc-service sshd start

Configure a passwd for the container

passwd

Setup acf for web administration

setup-acf

Setup Firewall

apk add acf-awall

With your favorite editor, create the policies for the firewall

Contents of /etc/awall/optional/base.json

{ "description": "Management", "policy": [ { "in": "_fw", "action": "accept" } ], "filter": [ { "out": "_fw", "service": [ "ssh", "https", "ping" ], "action": "accept" } ] }

Contents of /etc/awall/optional/sip.json

{ "description": "Phone System", "filter": [ { "out": "_fw", "service": [ "sip", "sip-tls" ], "action": "accept", } ] }

Contents of /etc/awall/optional/syslog.json

{ "description": "Syslog server", "filter": [ { "out": "_fw", "service": "syslog", "action": "accept" } ] }

Activate the firewall, and allow iptables to startup automatically at boot

awall enable base awall enable sip awall enable syslog awall activate -f rc-update add iptables

Install and Configure Postgresql

Install postgresql package

apk update apk add acf-postgresql

Prepare the database

rc-service postgresql setup

Configure /var/lib/postgresql/9.3/data/postgresql.conf to set the 'log_destination' variable to show:

Contents of /var/lib/postgresql/9.3/data/postresql.conf

.. log_destination ='syslog'

Start up the database and configure postgresql to start at boot up

rc-service postgresql start rc-update add postgresql

Install acf-provisioning

  • vi /etc/kamailio/kamctlrc
SIP_DOMAIN=sip.office.example.net
DBENGINE=PGSQL
DBHOST=127.0.0.1
DBNAME=openser
DBRWUSER=openser
DBRWPW="openser"
DBROUSER=openserro
DBROPW=openserro
DBROOTUSER="postgres"
  • yes | kamdbctl create openser
  • apk add acf-provisioning lua-socket lua-expat
  • Create /etc/provisioning/update_device_params.lua:
-- This is the script run after editing device params - basically only worried about extension and password
local functions, params, oldparams = ...

require("posix")
require("luasql.postgres")

local root = "/var/www/provisioning/htdocs/"
local b62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

APP.logevent("got to update_device_params script")

local function generatepw()
	-- generate a random 12-character alphanumeric string
	local file = io.open("/dev/urandom")
	local str = ""
	if file == nil then return nil end
	local size = 12
	while (size > 0 ) do
		local offset = (string.byte(file:read(1)) % 62) + 1
		str = str .. string.sub (b62, offset, offset)
		size = size - 1
	end
	return str
end

local function findip(mac)
	if not mac or mac == "" then
		return nil
	end
	local ipaddr = functions.getselectresponse("SELECT ip FROM provisioning_requests WHERE mac~*'"..mac.."'")
	if ipaddr and ipaddr[1] then
		return ipaddr[1].ip
	end
end

local function addfuturenotify(ipaddr, extension)
	local res, err = pcall(function()
		functions.runsqlcommand("DELETE FROM notify WHERE ipaddr='"..ipaddr.."' AND extension='"..extension.."'")
	end)
	if not res and err then
		if string.match(err, "relation \"(%S+)\" does not exist") then
			functions.runsqlcommand("CREATE TABLE notify (ipaddr text, extension text, seasoned boolean DEFAULT false)")
		else
			assert(res, err)
		end
	end
	-- if table missing, create it and delete again
	functions.runsqlcommand("INSERT INTO notify VALUES('"..ipaddr.."', '"..extension.."')")
end

local notify_device = function(mac, extension)
	local ipaddr = findip(mac)
	if ipaddr then
		APP.logevent("Notifying "..ipaddr.." to update for "..(mac or ""))
		os.execute("/etc/provisioning/notify_device "..ipaddr.." "..extension)
		addfuturenotify(ipaddr, extension)
	else
		APP.logevent("Warning - could not find IP address for "..(mac or ""))
	end
end

local kam = APP:new("kamailio/kamailio")

-- A table of devices to notify to update
local devices = {}

-- First, we check the registration numbers / passwords to 1) set a random password (if necessary) 2) make sure password matches any other registrations to same extension 3) push changes (add / delete / or update) to Kam database
-- Because this script also handles when device classes change, we have to consider that extensions are added / removed
local regs = {}
local forwarding = {"forwardnoanswerenable", "forwardnoanswer", "forwardbusyenable", "forwardbusy", "forwardallenable", "forwardall"}
local forwardsettings = {} -- Collect forwarding settings
local passwords = {} -- Collect old extension/password pairs
for name,val in pairs(params.value) do
	if not regs[name] and string.match(name, "^reg") then
		regs[name] = true
	end
end
oldparams = oldparams or {value={}}
for name,val in pairs(oldparams.value) do
	if string.match(name, "^reg") then
		if val.value and val.value.extension and val.value.password then
			passwords[val.value.extension.value] = val.value.password.value
		end
		if val.value and val.value.extension then
			local fwd = {}
			for i,f in ipairs(forwarding) do
				if val.value[f] then
					fwd[f] = val.value[f].value
				end
			end
			forwardsettings[val.value.extension.value] = fwd
		end
		if not regs[name] then
			regs[name] = true
		end
	end

end
local extension_id = params.value.reg1.value.extension.param_id
local password_id = params.value.reg1.value.password.param_id
functions.runsqlcommand("BEGIN TRANSACTION")
for name,val in pairs(regs) do
	local new = ""
	local old = ""
	if params.value[name] then new = params.value[name].value.extension.value end
	if oldparams.value[name] then old = oldparams.value[name].value.extension.value end
	if new ~= old then
		-- Extension changed
		-- First, let's remove the stuff for the old extension
		if old ~= "" then
			local others = functions.getselectresponse("SELECT count(*) FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..old.."'")
			if others[1].count == "0" then
				APP.logevent("Removing old registration "..old)
				-- remove the registration
				kam.model.delete_user(old)
			end
		end
		-- Now, add the new extension
		if new ~= "" then
			local pass
			if params.value[name].value.password.value ~= "" and (not oldparams.value[name] or (oldparams.value[name].value.password.value ~= params.value[name].value.password.value)) then
				-- The password parameter was changed and not blank, so use it
				pass = params.value[name].value.password.value
				APP.logevent("Added a new registration "..new.." with specified password "..pass)
				-- There may be other devices with this extension
				local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
				for i,o in ipairs(others) do
					-- We'll notify whether it changed or not
					devices[o.value] = new
				end
			elseif passwords[new] then
				pass = passwords[new]
				APP.logevent("Added a new registration "..new.." with reused password "..pass)
			else
				local others = functions.getselectresponse("SELECT * FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."' AND device_id!='"..params.value.device_id.value.."'")
				-- If this is a new registration, use a new password
				if #others == 0 then
					pass = generatepw()
					APP.logevent("Added a new registration "..new.." with random password "..pass)
				else
					-- Use the old password
					local p = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id='"..password_id.."' AND device_id='"..others[1].device_id.."' AND group_name='"..others[1].group_name.."' LIMIT 1")
					pass = p[1].value
					APP.logevent("Added a new registration "..new.." with reused password "..pass)
				end
			end
			passwords[new] = pass
			params.value[name].value.password.value = pass
			functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..password_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..password_id.."', '"..pass.."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			local u = kam.model.get_user(new)
			if u.value.username.errtxt then
				-- Add this registration to Kam
				local u = kam.model.get_new_user()
				u.value.username.value = new
				u.value.password.value = pass
				u.value.password_confirm.value = pass
				kam.model.create_new_user(u)
			else
				u.value.password.value = pass
				u.value.password_confirm.value = pass
				kam.model.update_user(u)
			end
			-- Let's also look at the forwarding settings
			local change = false
			local supported = false
			local fwd = {}
			for i,f in ipairs(forwarding) do
				if params.value[name].value[f] then
					supported = true
					fwd[f] = params.value[name].value[f].value
					if ((not oldparams.value[name] or not oldparams.value[name].value[f]) and (params.value[name].value[f].value ~= params.value[name].value[f].default)) or
						(oldparams.value[name] and oldparams.value[name].value[f] and (params.value[name].value[f].value ~= oldparams.value[name].value[f].value)) then
						change = true
					end
				end
				if change then
					local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
					for i,o in ipairs(others) do
						-- We'll notify whether it changed or not
						devices[o.value] = new
					end
				end
			end
			if supported then
				if not change and forwardsettings[new] then
					fwd = forwardsettings[new]
				elseif not change then
					-- This is a new extension, and the forwarding has not been set. We should check to see if there are any other devices with this extension
					local others = functions.getselectresponse("SELECT * FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."' AND device_id!='"..params.value.device_id.value.."'")
					if #others > 0 then
						-- Use the existing settings
						for i,f in ipairs(forwarding) do
							local v = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id='"..params.value[name].value[f].param_id.."' AND device_id='"..others[1].device_id.."' AND group_name='"..others[1].group_name.."' LIMIT 1")
							if #v > 0 then
								fwd[f] = v[1].value
							else
								fwd[f] = params.value[name].value[f].default
							end
						end
					end
				end
				if not change then
					for i,f in ipairs(forwarding) do
						params.value[name].value[f].value = fwd[f]
					end
				end
				forwardsettings[new] = fwd
				for i,f in ipairs(forwarding) do
					functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..params.value[name].value[f].param_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
					if fwd[f] ~= params.value[name].value[f].default then
						functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..params.value[name].value[f].param_id.."', '"..tostring(fwd[f]).."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
					end
				end
			end
		end
	elseif params.value[name] and oldparams.value[name] and params.value[name].value.extension.value ~= "" then
		if params.value[name].value.password.value ~= oldparams.value[name].value.password.value then
			-- Password changed - make any other registrations to this extension also change
			local pass = params.value[name].value.password.value
			if pass and pass ~= "" then
				APP.logevent("Password changed for "..new.." from "..oldparams.value[name].value.password.value.." to "..pass)
			else
				pass = generatepw()
				APP.logevent("Password cleared for "..new..", so set new random password "..pass)
			end
			passwords[new] = pass
			functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..password_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..password_id.."', '"..pass.."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			local u = kam.model.get_user(new)
			u.value.password.value = params.value[name].value.password.value
			u.value.password_confirm.value = params.value[name].value.password.value
			kam.model.update_user(u)
			-- Have to notify those other devices too
			local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			for i,o in ipairs(others) do
				devices[o.value] = new
			end
		end
		local change = false
		local fwd = {}
		for i,f in ipairs(forwarding) do
			if params.value[name].value[f] and (not oldparams.value[name].value[f] or params.value[name].value[f].value ~= oldparams.value[name].value[f].value) then
				change = true
				break
			end
		end
		if change then
			-- Forwarding settings changed - make any other registrations to this extension also change
			for i,f in ipairs(forwarding) do
				functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..params.value[name].value[f].param_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
				if params.value[name].value[f].value ~= params.value[name].value[f].default then
					functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..params.value[name].value[f].param_id.."', '"..tostring(params.value[name].value[f].value).."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
				end
			end
			-- Have to notify those other devices too
			local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			for i,o in ipairs(others) do
				devices[o.value] = new
			end
		end
	end
end
functions.runsqlcommand("COMMIT")

-- If reg1 or freepstn changed, then need to change free-pstn in kam
-- Since there can be multiple devices with the same reg1, we can't rely on params and oldparams
-- Check both reg's
--APP.logevent(session.serialize("params", params))
local reg = {}
if oldparams.value.reg1 and oldparams.value.reg1.value.extension and oldparams.value.reg1.value.extension.value ~= "" then
	reg[oldparams.value.reg1.value.extension.value] = false
end
if params.value.reg1 and params.value.reg1.value.extension and params.value.reg1.value.extension.value ~= "" then
	local pstn = false
	if params.value.routing and params.value.routing.value.freepstn then
		pstn = params.value.routing.value.freepstn.value
	end
	reg[params.value.reg1.value.extension.value] = pstn
end

APP.logevent("Looking at free-pstn")
for r,f in pairs(reg) do
	-- if we're not sure, check provisioning database
	if not f then
		-- Most devices will have free-pstn due to class-of-service, but can be overridden, so this can get tricky
		-- Check all devices where reg1 extension = r, and see what the freepstn value is for each
		local others = functions.getselectresponse("SELECT CASE WHEN v.value IS NOT NULL THEN v.value WHEN g2p.value IS NOT NULL THEN g2p.value ELSE p.value END AS value "..
			"FROM (devices_to_classes d2t JOIN provisioning_classes t USING(class_id) JOIN classes_to_param_groups t2g USING (class_id) JOIN provisioning_groups g USING(group_id) "..
			"JOIN param_groups_to_params g2p USING(group_id) JOIN provisioning_params p USING(param_id)) LEFT JOIN provisioning_values v ON (d2t.device_id=v.device_id AND p.param_id=v.param_id AND g.name=v.group_name ) "..
			"WHERE p.name='freepstn' AND d2t.device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND group_name='reg1' AND value='"..r.."')")
		for i,o in ipairs(others) do
			-- Now check the freepstn value for each one
			if o.value == "true" then
				f = true
				break
			end
		end
	end
	-- Now, check the Kamailio group table
	local alreadythere = false
	local entries = kam.model.list_table_entries("grp")
	for i,e in ipairs(entries.value.entries.value) do
		if e.username == r then
			alreadythere = true
			if not f then
				APP.logevent("Removing free-pstn for "..r)
				-- Remove free-pstn from the old extension
				kam.model.delete_table_entry("grp", e.id)
			end
			break
		end
	end
	if f and not alreadythere then
		APP.logevent("Adding free-pstn for "..r)
		-- Add free-pstn to the new extension
		local e = kam.model.get_table_entry("grp")
		e.value.username.value = r
		e.value.domain.value = ""
		e.value.grp.value = "free-pstn"
		e.value.last_modified.value = os.date("%c")
		kam.model.create_table_entry(e)
	end
end

kam:destroy()

-- If the mac address changed for Polycom with valid MAC (not blank or all 0's), we need to move the associated config files
if oldparams.value.device and oldparams.value.device.value.mac and oldparams.value.device.value.mac.value ~= "" and params.value.device and params.value.device.value.mac and params.value.device.value.mac.value ~= oldparams.value.device.value.mac.value then
	if string.match(oldparams.value.device.label, "Polycom") and string.match(oldparams.value.device.value.mac.value, "[1-9A-F]") then
		local deletefiles = true
		if string.match(params.value.device.value.mac.value, "[1-9A-F]") then
			--APP.logevent("Moving files for "..oldparams.value.device.value.mac.value)
			deletefiles = false
		else
			--APP.logevent("Deleting files for "..oldparams.value.device.value.mac.value)
		end
		local path = root.."Polycom/"
		if posix.stat(path, "type") == "directory" then
	                for d in posix.files(path) do
        	                if string.match(d, string.lower(oldparams.value.device.value.mac.value)) and posix.stat(path..d, "type") == "regular" then
                	                local newfile = string.gsub(d, string.lower(oldparams.value.device.value.mac.value), string.lower(params.value.device.value.mac.value))
					if deletefiles then
	                        	        --APP.logevent("deleting "..path..d)
        	                        	os.remove(path..d)
					else
	                        	        --APP.logevent("moving "..path..d.." to "..path..newfile)
        	                        	os.rename(path..d, path..newfile)
					end
				end
                        end
                end
        end
end

-- Then, notify the phone to pull it's config
-- Try to get a valid extension currently on the device
local oldexten = ""
for name,val in pairs(oldparams.value) do
	if string.match(name, "^reg") and val.value.extension and val.value.extension.value ~= "" then
		oldexten = val.value.extension.value
		break
	end
end
if params.value.device and params.value.device.value.mac and params.value.device.value.mac.value ~= "" then
	devices[params.value.device.value.mac.value] = oldexten
end
if oldparams.value.device and oldparams.value.device.value.mac and oldparams.value.device.value.mac.value ~= "" then
	devices[oldparams.value.device.value.mac.value] = oldexten
end

for name,value in pairs(devices) do
	notify_device(name,value)
end
  • Create /etc/provisioning/provisioning_db_script
#!/usr/bin/lua

local path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin "
local creation_script = {
	-- Parameters
	"INSERT INTO provisioning_params VALUES(default, 'freepstn', 'boolean', 'Free PSTN Access', '', 'false', '200', '')",

	-- Parameter Groups
	"INSERT INTO provisioning_groups VALUES(default, 'routing', 'Free PSTN Access', '31')",
	"INSERT INTO provisioning_groups VALUES(default, 'routing', 'No Free PSTN Access', '32')",

	-- param_groups_to_params (group_id, param_id, value, editable)
	"INSERT INTO param_groups_to_params VALUES((SELECT group_id FROM provisioning_groups WHERE label='Free PSTN Access'), (SELECT param_id FROM provisioning_params WHERE name='freepstn'), 'true', false)",
	"INSERT INTO param_groups_to_params VALUES((SELECT group_id FROM provisioning_groups WHERE label='No Free PSTN Access'), (SELECT param_id FROM provisioning_params WHERE name='freepstn'), 'false', false)",
	
	-- Classes
	-- provisioning_class_groups (class_group_id, name, label, seq)
	"INSERT INTO provisioning_class_groups VALUES(default, 'routing', 'Routing', '3')",

	-- provisioning_classes (class_id, class_group_id, label, seq)
	"INSERT INTO provisioning_classes VALUES(default, (SELECT class_group_id FROM provisioning_class_groups WHERE name='routing'), 'Free PSTN Access', '1')",
	"INSERT INTO provisioning_classes VALUES(default, (SELECT class_group_id FROM provisioning_class_groups WHERE name='routing'), 'No Free PSTN Access', '2')",

	-- classes_to_param_groups (class_id, group_id)
	"INSERT INTO classes_to_param_groups VALUES((SELECT class_id FROM provisioning_classes WHERE label='Free PSTN Access'), (SELECT group_id FROM provisioning_groups WHERE label='Free PSTN Access'))",
	"INSERT INTO classes_to_param_groups VALUES((SELECT class_id FROM provisioning_classes WHERE label='No Free PSTN Access'), (SELECT group_id FROM provisioning_groups WHERE label='No Free PSTN Access'))",

	-- provisioning_options
	"INSERT INTO provisioning_options VALUES((SELECT param_id FROM provisioning_params WHERE name='polycomringtone'), 'Warble', '15', '15')",
	"INSERT INTO provisioning_options VALUES((SELECT param_id FROM provisioning_params WHERE name='polycomringtone'), 'Analog Ring', '16', '16')",
}


local f = io.popen("/usr/share/acf/www/cgi-bin/cli /provisioning/provisioning/getdevicevalues")
print(f:read("*a"))
f:close()
for i,c in ipairs(creation_script) do
	print(path..'psql -U postgres -c "'..c..'" provisioning 2>&1')
	local f = io.popen(path..'psql -U postgres -c "'..c..'" provisioning 2>&1')
	print(f:read("*a"))
	f:close()
end
  • chmod 755 /etc/provisioning/provisioning_db_script
  • /etc/provisioning/provisioning_db_script
  • echo '* * * * * run-parts /etc/periodic/1min' >> /etc/crontabs/root
  • mkdir /etc/periodic/1min/
  • /etc/periodic/1min/notify_device
#!/usr/bin/lua

-- Load libraries
require("luasql.postgres")

-- Set variables
local DatabaseName = "provisioning"
local DatabaseUser = "postgres"
local DatabasePassword

local path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin "
local env
local con

-- ################################################################################
-- LOCAL FUNCTIONS
local function assert (v, m)
	if not v then
		m = m or "Assertion failed!"
		error(m, 0)
	end
	return v, m
end

-- Escape special characters in sql statements
local escape = function(sql)
	sql = sql or ""
	sql = string.gsub(sql, "'", "''")
	return string.gsub(sql, "\\", "\\\\")
end

local databaseconnect = function()
	if not con then
		-- create environment object
		env = assert (luasql.postgres())
		-- connect to data source
		local err
		con, err = assert(env:connect(DatabaseName, DatabaseUser, DatabasePassword))
		return true
	end
	return false
end

local databasedisconnect = function()
	if env then
		env:close()
		env = nil
	end
	if con then
		con:close()
		con = nil
	end
end

local getselectresponse = function(sql)
	local retval = {}
	local cur = assert (con:execute(sql))
	local row = cur:fetch ({}, "a")
	while row do
		local tmp = {}
		for name,val in pairs(row) do
			tmp[name] = val
		end
		retval[#retval + 1] = tmp
		row = cur:fetch (row, "a")
	end
	cur:close()
	return retval
end

databaseconnect()

-- First, let's notify for the seasoned requests
local reqs = getselectresponse("SELECT * FROM notify WHERE seasoned='true'")
for i,r in ipairs(reqs) do
	os.execute("/etc/provisioning/notify_device "..r.ipaddr.." "..r.extension)
end

-- Then mark any others as seasoned
assert(con:execute("DELETE FROM notify WHERE seasoned='true'"))
assert(con:execute("UPDATE notify SET seasoned='true' WHERE seasoned='false'"))

databasedisconnect()
  • apk add lighttpd
  • rm /etc/lighttpd/lighttpd.conf
  • ln -s /etc/provisioning/lighttpd.sample.conf /etc/lighttpd/lighttpd.conf
  • /etc/lighttpd/mod_cgi.conf:
###############################################################################
# mod_cgi.conf
# include'd by lighttpd.conf.
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/mod_cgi.conf,v 1.1 2005/08/27 12:36:13 ka0ttic Exp $
###############################################################################

#
# see cgi.txt for more information on using mod_cgi
#

server.modules += ("mod_cgi")

# NOTE: this requires mod_alias
alias.url = (
     "/cgi-bin/"	    =>	    var.basedir + "/cgi-bin/"
)

#
# Note that you'll also want to enable the
# cgi-bin alias via mod_alias (above).
#

$HTTP["url"] =~ "^/cgi-bin/" {
    # disable directory listings
    dir-listing.activate = "disable"
    # only allow cgi's in this directory
    cgi.assign = (
		".pl"	=>	"/usr/bin/perl",
		".cgi"	=>	"",
		""	=>	""
	)
}

# vim: set ft=conf foldmethod=marker et :
  • rc-service lighttpd start
  • rc-update add lighttpd
  • Customize provisioning:
Parameter	Default Value
registrar	 IP address or host name of SIP Router(s)
digitmap	 Digit map for your phone system (See Polycom Digit Map Reference)
digitmaptimeout	 Timeout in seconds corresponding to digitmap
sntpserver	 10.2.0.1 (DMVPN spoke node)
timezone	 Timezone information for this location - see below
musiconhold	 SIP uri of the music-on-hold service - moh@media.office.example.net
adminpassword	 Administration password for advanced settings on phone (on-screen and web interface)
  • apk fetch --stdout acf-provisioning-polycom | tar -C / -zx
  • Create a test Polycom device and boot it in the voice network to verify that it works

Install Kamailio

  • apk add kamailio kamailio-presence kamailio-pcre kamailio-postgres
  • rc-service kamailio start
  • rc-update add kamailio
  • FUTURE: POST CONFIG THAT USES SUBSCRIBER TABLE FOR AUTH

Install the SIP Media container

Create and Configure the container

lxc-create -n sipmedia -f /etc/lxc/default.conf -t alpine

Create the startup Script

ln -s lxc /etc/init.d/lxc.sipmedia

Edit the container's config file found at /var/lib/lxc/sipmedia/config, to reflect the network for the SIP Media container

Contents of /var/lib/lxc/sipmedia/config

... lxc.network.link = bond0.1101 ...

Start the container

rc-service lxc.sipmedia

Configure the container to automatically start

rc-update add lxc.sipmedia

Enter the SIP Media container

lxc-console -n sipmedia

Login as root

Note: If the need arises to exit the container press Ctrl+ a + q

Remove obsolete /etc/network/interfaces

rm /etc/network/interfaces

Create and configure the new /etc/network/interfaces as shown below:

Contents of /etc/network/interfaces

auto lo iface lo inet loopback auto eth0 iface eth0 inet static address 10.2.0.5 netmask 255.255.255.0 gateway 10.2.0.1

Startup networking

rc-service networking start

Configure and enable proxy settings

setup-proxy http://10.1.0.2:8080 . /etc/profile.d/proxy.sh

Configure remote administration

apk update setup-sshd -c openssh sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config

Start ssh

rc-service sshd start

Configure a passwd for the container

passwd

Setup acf for web administration

setup-acf

Setup Firewall

apk add acf-awall

With your favorite editor, create the policies for the firewall

Contents of /etc/awall/optional/base.json

{ "description": "Management", "policy": [ { "in": "_fw", "action": "accept" } ], "filter": [ { "out": "_fw", "service": [ "ssh", "https", "ping" ], "action": "accept" } ] }

Contents of /etc/awall/optional/sip-track.json

{ "description": "Phone system with SIP connection tracking", "filter": [ { "out": "_fw", "service": [ "sip", "sip-tls" ], "action": "accept" } ] }

Enable and activate firewall policies, and configure iptables to start at boot

awall enable base awall enable sip-track awall activate -f rc-update add iptables

Install and Configure Freeswitch

Install package

Install Freeswitch Package

Configure /etc/freeswitch/freeswitch.xml

Contents of /etc/freeswitch/freeswitch.xml

...
    <extension name="hold_music">                                                                                                                                                                              
      <condition field="destination_number" expression="^moh$">                                                                                                                                                
        <action application="answer"/>                                                                                                                                                                         
        <action application="playback" data="$${hold_music}"/>                                                                                                                                                 
      </condition>                                                                                                                                                                                             
    </extension>
...
<X-PRE-PROCESS cmd="set" data="hold_music=local_stream://default"/>
...
FUTURE: ADD VOICEMAIL

Start Freeswitch and configure to start at boot

rc-service freeswitch start rc-update add freeswitch

Install the WiFi Web Proxy Container

Create and Configure the container

lxc-create -n wifi -f /etc/lxc/default.conf -t alpine

Create the startup Script

ln -s lxc /etc/init.d/lxc.wifi

Edit the container's config file found at /var/lib/lxc/wifi/config, to reflect the network for the wifi container

Contents of /var/lib/lxc/wifi/config

... lxc.network.link = bond0.701 ...

Start the container

/etc/iniit.d/lxc.wifi

Configure the container to automatically start

rc-update add lxc.wifi

Enter the wifi container

lxc-console -n wifi

Login as root

Note: If the need arises to exit the container press Ctrl+ a + q

Remove obsolete /etc/network/interfaces

rm /etc/network/interfaces

Create and configure the new /etc/network/interfaces as shown below:

Contents of /etc/network/interfaces

auto lo iface lo inet loopback auto eth0 iface eth0 inet static address 172.17.48.1 netmask 255.255.255.0 auto eth1 iface eth1 inet static address 10.1.0.254 netmask 255.255.255.252 gateway 10.1.0.253 auto eth2 iface eth2 inet static address 10.1.0.131 netmask 255.255.255.192

Startup networking

rc-service networking start


Configure remote administration

apk update setup-sshd -c openssh sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config

Start ssh

rc-service sshd start

Configure a passwd for the container

passwd

Setup acf for web administration

setup-acf

Setup Firewall

apk add acf-awall

Todo: Need to lock down firewall rules


Install and Configure the Recursive DNS Service

Install unbound package

apk add unbound

With your favorite editor configure /etc/unbound/unbound.conf

Contents of /etc/unbound/unobund.conf

server: verbosity: 1 interface: 172.17.48.1 do-ip4: yes do-ip6: no do-udp: yes do-tcp: yes do-daemonize: yes access-control: 172.17.0.0/16 allow access-control: 127.0.0.0/8 allow do-not-query-localhost: no root-hints: "/etc/unbound/root.hints" python: remote-control: control-enable: no

Install and Configure the Proxy service

Install the necessary packages

apk add squid squark lighttpd

With your preferred editor configure /etc/squid/squid.conf

Contents of /etc/squid/squid.conf

#Squid config 

# This port listens for client requests
http_port 172.17.48.1:8080 transparent
http_port 127.0.0.1:8081

visible_hostname wifi.local
cache_mem 8 MB
# If you don't have an HD installed comment the "cache_dir" line below
cache_dir aufs /var/cache/squid 900 16 256

# Even though we only use one proxy, this line is recommended
# More info: http://www.squid-cache.org/Versions/v2/2.7/cfgman/hierarchy_stoplist.html
hierarchy_stoplist cgi-bin ?

# Keep 7 days of access logs
logfile_rotate 7

logformat squark %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %un %Sh/%<A %mt %rG
access_log /var/log/squid/access.log squark
cache_store_log none
pid_filename /var/run/squid.pid

# Make sure client IP is passed to Squark
log_uses_indirect_client on
acl_uses_indirect_client on

# Debugging Squid, see http://wiki.squid-cache.org/KnowledgeBase/DebugSections
# for more info
# Keep 7 days of cache log
debug_options rotate=7

# Web auditors want to see the full uri, even with the query terms
strip_query_terms off

refresh_pattern ^ftp:		1440	20%	10080
refresh_pattern ^gopher:	1440	0%	1440
refresh_pattern -i (/cgi-bin/|\?) 0	0%	0
refresh_pattern .		0	20%	4320

coredump_dir /var/cache/squid

dns_nameservers 172.17.48.1

# 
# Authentication
#
# Squark external acl
#external_acl_type squark_snmp_auth_D children-max=1 ttl=4 grace=1 negative_ttl=0 concurrency=128 %SRC /usr/bin/squark-auth-snmp -c public -R <SWITCH_IP> -i <D_VLAN_IF> -v <D_VLAN_ID> -f "%N-%i=%I" -T /etc/squark/topology.conf

#
# Access Control Lists (ACL's)
#

# Standard ACL settings
acl QUERY urlpath_regex cgi-bin \? asp aspx jsp
acl to_localhost dst 172.17.48.1
acl SSL_ports port 443 563 8004 9000
acl Safe_ports port 21 70 80 81 210 280 443 563 499 591 777 1024 1022 1025-65535
acl purge method PURGE
acl CONNECT method CONNECT

#acl SquarkAuth external squark_auth
#acl SquarkSnmpAuthD external squark_snmp_auth_D

# Squark filter
url_rewrite_program /usr/bin/squark-filter
url_rewrite_children 1 concurrency=128

# Require authentication
acl userlist  src all

# Definition of zones
acl Zone_D src 172.17.48.0/24


#
# Access restrictions
#

cache deny QUERY

# Only allow cachemgr access from localhost
http_access allow manager localhost
http_access deny manager

# Only allow purge requests from localhost
http_access allow purge localhost
http_access deny purge

# Deny requests to unknown ports
http_access deny !Safe_ports

# Deny CONNECT to other than SSL ports
http_access deny CONNECT !SSL_ports

# Allow hosts in Zone_D to access the entire Internet
http_access allow Zone_D

# Denying all access not explictly allowed
http_access deny all

##Squark URL rewriter
#Prevent squark from filtering itself
url_rewrite_access deny manager
url_rewrite_access deny to_localhost

#Finally, permit access
url_rewrite_access allow Zone_D

http_reply_access allow all
icp_access allow all

Configure lighttpd

Contents of /etc/lighttpd/lighttpd.conf

var.basedir  = "/var/www/localhost"
var.logdir   = "/var/log/lighttpd"
var.statedir = "/var/lib/lighttpd"

server.modules = (
    "mod_access",
    "mod_accesslog",
    "mod_extforward"
)

include "mime-types.conf"
include "mod_cgi.conf"

server.username      = "lighttpd"
server.groupname     = "lighttpd"

server.document-root = var.basedir + "/squark"
server.pid-file      = "/var/run/lighttpd.pid"

server.errorlog      = var.logdir  + "/error.log"

server.indexfiles    = ("index.php", "index.html",
						"index.htm", "default.htm")


server.follow-symlink = "enable"

server.port          = 81
server.bind          = "172.17.48.1"

static-file.exclude-extensions = (".php", ".pl", ".cgi", ".fcgi")

accesslog.filename   = var.logdir + "/access.log"

url.access-deny = ("~", ".inc")

extforward.forwarder = ("172.17.48.1" => "trust")

Contents of /etc/lighttpd/mod_cgi.conf

###############################################################################
# mod_cgi.conf
# include'd by lighttpd.conf.
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/mod_cgi.conf,v 1.1 2005/08/27 12:36:13 ka0ttic Exp $
###############################################################################

#
# see cgi.txt for more information on using mod_cgi
#

server.modules += ("mod_cgi")

# NOTE: this requires mod_alias
alias.url = (
     "/cgi-bin/"	    =>	    var.basedir + "/cgi-bin/"
)

#
# Note that you'll also want to enable the
# cgi-bin alias via mod_alias (above).
#

$HTTP["url"] =~ "^/cgi-bin/" {
    # disable directory listings
    dir-listing.activate = "disable"
    # only allow cgi's in this directory
    cgi.assign = (
		".pl"	=>	"/usr/bin/perl",
		".cgi"	=>	"/usr/bin/haserl"
	)
}

# vim: set ft=conf foldmethod=marker et :

Link Squark web pages to the Web server home directory

ln -s /usr/share/squark/www/ /var/www/localhost/squark

Make 'squid' and 'lighttpd' users member of the group squark

addgroup squid squark addgroup lighttpd squark

Start lighttpd and configure the Web service to start at boot

rc-service lighttpd start rc-update add lighttpd

Start Squid and configure it to start at boot

rc-service squid start rc-update add squid

See also