mbox

[RFC,1/2] vpn-security-policies: add new feature vpn security-policies

Message ID 1499970814-14953-2-git-send-email-jonatan.schlag@ipfire.org
State Superseded
Headers

Message

Jonatan Schlag July 14, 2017, 4:33 a.m. UTC
  For further explanation see:
http://wiki.ipfire.org/devel/network/security-policies

Signed-off-by: Jonatan Schlag <jonatan.schlag@ipfire.org>
---
 src/functions/functions.vpn-security-policies | 437 ++++++++++++++++++++++++++
 1 file changed, 437 insertions(+)
 create mode 100644 src/functions/functions.vpn-security-policies
  

Comments

Michael Tremer July 14, 2017, 9:24 a.m. UTC | #1
Hi,

On Thu, 2017-07-13 at 20:33 +0200, Jonatan Schlag wrote:
> For further explanation see:
> http://wiki.ipfire.org/devel/network/security-policies
> 
> Signed-off-by: Jonatan Schlag <jonatan.schlag@ipfire.org>
> ---
>  src/functions/functions.vpn-security-policies | 437
> ++++++++++++++++++++++++++
>  1 file changed, 437 insertions(+)
>  create mode 100644 src/functions/functions.vpn-security-policies
> 
> diff --git a/src/functions/functions.vpn-security-policies
> b/src/functions/functions.vpn-security-policies
> new file mode 100644
> index 0000000..9e24e4f
> --- /dev/null
> +++ b/src/functions/functions.vpn-security-policies
> @@ -0,0 +1,437 @@
> +#!/bin/bash
> +####################################################################
> ###########
> +#                                                                   
>           #
> +# IPFire.org - A linux based
> firewall                                         #
> +# Copyright (C) 2013  IPFire Network Development
> Team                         #
> +#                                                                   
>           #

It's 2017 :)

> +# This program is free software: you can redistribute it and/or
> modify        #
> +# it under the terms of the GNU General Public License as published
> by        #
> +# the Free Software Foundation, either version 3 of the License,
> or           #
> +# (at your option) any later
> version.                                         #
> +#                                                                   
>           #
> +# This program is distributed in the hope that it will be
> useful,             #
> +# but WITHOUT ANY WARRANTY; without even the implied warranty
> of              #
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
> the               #
> +# GNU General Public License for more
> details.                                #
> +#                                                                   
>           #
> +# You should have received a copy of the GNU General Public
> License           #
> +# along with this program.  If not, see <http://www.gnu.org/licenses
> />.       #
> +#                                                                   
>           #
> +####################################################################
> ###########
> +
> +VPN_SECURITY_POLICIES_CONFIG_SETTINGS="CIPHER COMPRESSION GROUP_TYPE
> INTEGRITY KEY_EXCHANGE LIFETIME PFS"
> +VPN_SECURITY_POLICIES_READONLY="system"
> +
> +VPN_SUPPORTED_CIPHERS="AES192 AES256 AES128"
> +VPN_SUPPORTED_INTEGRITY="SHA512 SHA256 SHA128"
> +VPN_SUPPORTED_GROUP_TYPES="MODP4096 MODP8192"

Obviously these need to be extended. But this will suit as a proof-of-
concept. Just please order stuff alphabetically. In this case it is
best to do it descending.

> +vpn_security_policies_check_readonly() {
> +	# This functions checks if a policy is readonly
> +	# returns true when yes and false when no
> +
> +	if isoneof name ${VPN_SECURITY_POLICIES_READONLY}; then
> +		return ${EXIT_TRUE}
> +	else
> +		return ${EXIT_FALSE}
> +	fi
> +}

Ok.

> +vpn_security_policies_write_config() {
> +	# This function writes all values to a via ${name}
> specificated vpn security policy configuration file
> +	assert [ $# -ge 1 ]
> +
> +	local name="${1}"
> +
> +	if ! vpn_security_policy_exists ${name}; then
> +		log ERROR "No such policy: ${name}"
> +		return ${EXIT_ERROR}
> +	fi

I think you need to improve some of the error messages. A sentence is
usually good. The shorter the sweeter. Not sure if "no such policy" is
helpful when they are actually called security policies. So, don't go
too short.

> +
> +	if vpn_security_policies_check_readonly ${name}; then
> +		log ERROR "${name} is a readable only vpn security
> policy"
> +		return ${EXIT_ERROR}
> +	fi

Here I would suggest: "The system security policy cannot be changed"

> +
> +	local args
> +	list_append args ${VPN_SECURITY_POLICIES_CONFIG_SETTINGS}

No need to call list_append here. Settings args to
VPN_SECURITY_POLICIES_CONFIG_SETTINGS is enough. You just just pass it
to settings_write without using $args.

> +
> +	local path="$(vpn_security_policies_path ${name})"
> +	if [ ! -w ${path} ]; then
> +		log ERROR "${path} is not writeable"
> +		return ${EXIT_ERROR}
> +	fi
> +
> +	settings_write "${path}" ${args}
> +	# TODO everytime we successfully write a config we should
> call some trigger to take the changes into effect
> +}

Space between the settings_write and comments, please.

What happens when the settings could not be written?

> +vpn_security_policies_write_config_key() {
> +	# This funtion writes the value for one key to a via ${name}
> specificated vpn security policy configuration file
> +	assert [ $# -ge 3 ]
> +	local name=${1}
> +
> +	if ! vpn_security_policy_exists ${name}; then
> +		log ERROR "No such policy: ${name}"
> +		return ${EXIT_ERROR}
> +	fi
> +
> +	local key=${2}
> +	shift 2
> +	local value="$@"
> +	log DEBUG "Set '${key}' to new value '${value}'"

Please group the argument parsing together at the top and then test if
the policy exists. This is hard to read.

Also the logging message is a bit random since it doesn't mention the
security policy here.

> +	# Read the config settings
> +	if ! vpn_security_policies_read_config ${name}; then
> +		return ${EXIT_ERROR}
> +	fi

This will overwrite any global variables. So you will need to
call

  local VPN_SECURITY_POLICIES_CONFIG_SETTINGS

before you read the settings from file.

> +	# Set the key to a new value
> +	eval "${key}=\"${value}\""

Never ever use eval. This will allow someone to pass shell commands
that may get executed. At no time ever we would want that.

There is a function called assign() that takes care of assigning a
variable.

> +	if ! vpn_security_policies_write_config ${name}; then
> +		return ${EXIT_ERROR}
> +	fi
> +
> +	return ${EXIT_TRUE}
> +
> +}
> +
> +vpn_security_policies_read_config() {
> +	# Reads one or more keys out of a settings file or all if no
> key is provided.
> +	assert [ $# -ge 1 ]
> +
> +	local name="${1}"
> +	shift 1
> +
> +	if ! vpn_security_policy_exists ${name}; then
> +		log ERROR "No such policy: ${name}"
> +		return ${EXIT_ERROR}
> +	fi
> +
> +
> +	local args
> +	if [ $# -eq 0 ] && [ -n
> "${VPN_SECURITY_POLICIES_CONFIG_SETTINGS}" ]; then
> +		list_append args
> ${VPN_SECURITY_POLICIES_CONFIG_SETTINGS}
> +	else
> +		list_append args $@
> +	fi
> +
> +	local path="$(vpn_security_policies_path ${name})"
> +	if [ ! -r ${path} ]; then
> +		log ERROR "${path} is not readable"
> +		return ${EXIT_ERROR}
> +	fi

Why do you need to do this every time? Doesn't settings_read do this?

> +
> +	settings_read "${path}" ${args}
> +}
> +
> +vpn_security_policies_path() {
> +	# Returns the path to a the configuration fora given name
> +	assert [ $# -eq 1 ]
> +	local name=${1}
> +
> +	if vpn_security_policies_check_readonly ${name}; then
> +		echo "/usr/share/network/vpn/security-
> policies/${name}"
> +	else
> +		echo "/etc/network/vpn/security-policies/${name}"
> +	fi
> +}

Hard-coded paths :(

> +
> +vpn_security_policies_show() {
> +	# Print the content of a vpn security policy configuration
> file in a nice way
> +	assert [ $# -eq 1 ]
> +	local name=${1}

Here you will need to do the local variable initialization before you
red this in - as above.

> +	# Break if read fails
> +	if ! vpn_security_policies_read_config ${name}; then
> +		return ${EXIT_ERROR}
> +	fi
> +
> +	cli_print_fmt1 0 "Security Policy: ${name}"
> +	cli_space
> +
> +	# This could be done in a loop but a loop is much more
> complicated
> +	# because we print 'Group Types' but the variable is named
> 'GROUP_TYPES'
> +	cli_print_fmt1 1 "Ciphers:"
> +	cli_print_fmt1 2 "${CIPHER}"
> +	cli_space

This does not show any broken ciphers as broken. But we could add this
later as well. This patch is quite huge already.

> +	cli_print_fmt1 1 "Integrity:"
> +	cli_print_fmt1 2 "${INTEGRITY}"
> +	cli_space
> +	cli_print_fmt1 1 "Group Types:"
> +	cli_print_fmt1 2 "${GROUP_TYPE}"
> +	cli_space
> +
> +	cli_print_fmt1 1 "Key exchange:" "${KEY_EXCHANGE}"

Capital E in Exchange

> +	cli_print_fmt1 1 "Key Lifetime in seconds:" "${LIFETIME}"

The lifetime should be formatted nicely because seconds are hard to
understand. There is a function that formats these nicely.

> +	if enabled PFS; then
> +		cli_print_fmt1 1 "Perfect Forward Secrecy:"
> "enabled"
> +	else
> +		cli_print_fmt1 1 "Perfect Forward Secrecy:"
> "disabled"
> +	fi
> +	cli_space
> +	if enabled COMPRESSION; then
> +		cli_print_fmt1 1 "Compression:" "enabled"
> +	else
> +		cli_print_fmt1 1 "Compression:" "disabled"
> +	fi
> +	cli_space
> +}
> +
> +vpn_security_policy_exists() {
> +	# This function checks if a vpn security policy exists
> +	# Returns True when yes and false when not
> +	assert [ $# -eq 1 ]
> +	local name=${1}
> +
> +	local path=$(vpn_security_policies_path ${name})
> +	[ -f ${path} ]
> +}
> +
> +
> +vpn_security_policies_cipher(){
> +	# This function parses the parameters for the 'cipher'
> command
> +	local name=${1}
> +	shift
> +
> +	if [ $# -eq 0 ]; then
> +		log ERROR "You must pass at least one value after
> cipher"
> +		return ${EXIT_ERROR}
> +	fi
> +

local CIPHER

> +	if ! vpn_security_policies_read_config ${name} "CIPHER";
> then
> +		return ${EXIT_ERROR}
> +	fi
> +
> +	# Remove duplicated entries to proceed the list safely
> +	CIPHER="$(list_unique ${CIPHER})"
> +
> +	while [ $# -gt 0 ]; do
> +		case "${1}" in
> +			-*)
> +				value=${1#-}
> +				# Check if the cipher is in the list
> of ciphers and
> +				# check if the list has after
> removing this cipher at least one valid value
> +				if list_match ${value} ${CIPHER} &&
> [ $(list_length ${CIPHER}) -gt 1 ]; then
> +					list_remove CIPHER ${value}
> +				else
> +					log ERROR "Can not remove
> ${value} from the list of Ciphers because ${value} is not in the list
> or the list would be empty after removing this cipher"
> +				fi

There is no return here which ends the function on this error.

I think you should check this after the loop to support things like:

-3DES +AES256

This would crash if 3DES is the last cipher but the user does not
intend to remove all ciphers.

> +				;;
> +			+*)
> +				value=${1#+}
> +				# Check if the Ciphers is in the
> list of supported ciphers.
> +				if ! isoneof value
> ${VPN_SUPPORTED_CIPHERS}; then
> +					log ERROR "${value} is not a
> supported cipher and can thats why not added to the list of ciphers"
> +				else
> +					if list_match ${value}
> ${CIPHER}; then
> +						log WARNING
> "${value} is already in the list of ciphers of this policy"
> +					else
> +						list_append CIPHER
> ${value}
> +					fi
> +				fi
> +				;;
> +		esac
> +		shift
> +	done
> +
> +	vpn_security_policies_write_config_key ${name} "CIPHER"
> ${CIPHER}

Are there no errors to catch here?

> +}
> +
> +vpn_security_policies_compression(){
> +	# This function parses the parameters for the 'compression'
> command
> +	local name=${1}
> +	local value=${2}
> +	# Check if we get only one argument after compression <name>
> +	if [ ! $# -eq 2 ] || ! isbool value; then
> +		log ERROR "You can pass only one parameter after
> compression and this one must be 'on' or 'off'"
> +		return ${EXIT_ERROR}
> +	fi

Probably not the nicest handling in case of no argument or so. This
also suggests two values but actually more would be accepted as well.
> +
> +	vpn_security_policies_write_config_key "${name}"
> "COMPRESSION" "${value}"
> +}
> +
> +vpn_security_policies_group_type(){
> +	# This function parses the parameters for the 'group-type'
> command.
> +	local name=${1}
> +	shift
> +
> +	if [ $# -eq 0 ]; then
> +		log ERROR "You must pass at least one value after
> group-type"
> +		return ${EXIT_ERROR}
> +	fi
> +
> +	if ! vpn_security_policies_read_config ${name} "GROUP_TYPE";
> then
> +		return ${EXIT_ERROR}
> +	fi
> +
> +	# Remove duplicated entries to proceed the list safely
> +	GROUP_TYPE="$(list_unique ${GROUP_TYPE})"
> +
> +	while [ $# -gt 0 ]; do
> +		case "${1}" in
> +			-*)
> +				value=${1#-}
> +				# Check if the group type is in the
> list of group types and
> +				# check if the list has after
> removing this group type at leatst one valid value
> +				if list_match ${value} ${GROUP_TYPE}
> && [ $(list_length ${GROUP_TYPE}) -gt 1 ]; then
> +					list_remove GROUP_TYPE
> ${value}
> +				else
> +					log ERROR "Can not remove
> ${value} from the list of group types because ${value} is not in the
> list or the list would be empty after removing this group type"
> +				fi
> +				;;
> +			+*)
> +				value=${1#+}
> +				# Check if the group type is in the
> list of supported group types.
> +				if ! isoneof value
> ${VPN_SUPPORTED_GROUP_TYPES}; then
> +					log ERROR "${value} is not a
> supported group type and can thats why not added to the list of group
> types"
> +				else
> +					if list_match ${value}
> ${GROUP_TYPE}; then
> +						log WARNING
> "${value} is already in the list of group-types of this policy"
> +					else
> +						list_append
> GROUP_TYPES ${value}
> +					fi
> +				fi
> +				;;
> +		esac
> +		shift
> +	done
> +
> +	vpn_security_policies_write_config_key ${name} "GROUP_TYPE"
> ${GROUP_TYPE}
> +}

Same as above.

> +
> +vpn_security_policies_integrity(){
> +	# This function parses the parameters for the 'integrity'
> command
> +	local name=${1}
> +	shift
> +
> +	if [ $# -eq 0 ]; then
> +		log ERROR "You must pass at least one value after
> integrity"
> +		return ${EXIT_ERROR}
> +	fi
> +
> +	if ! vpn_security_policies_read_config ${name} "INTEGRITY";
> then
> +		return ${EXIT_ERROR}
> +	fi
> +
> +	# Remove duplicated entries to proceed the list safely
> +	INTEGRITY="$(list_unique ${INTEGRITY})"
> +
> +	while [ $# -gt 0 ]; do
> +		case "${1}" in
> +			-*)
> +				value=${1#-}
> +				# Check if the integrity hash is in
> the list of integrity hashes and
> +				# check if the list has after
> removing this  integrity hash at least one valid value
> +				if list_match ${value} ${INTEGRITY}
> && [ $(list_length ${INTEGRITY}) -gt 1 ]; then
> +					list_remove INTEGRITY
> ${value}
> +				else
> +					log ERROR "Can not remove
> ${value} from the list of integrity hashes because ${value} is not in
> the list or the list would be empty after removing this integrity
> hash"
> +				fi
> +				;;
> +			+*)
> +				value=${1#+}
> +				# Check if the Ciphers is in the
> list of supported integrity hashes.
> +				if ! isoneof value
> ${VPN_SUPPORTED_INTEGRITY}; then
> +					log ERROR "${value} is not a
> supported integrity hashes and can thats why not added to the list of
> integrity hashes"
> +				else
> +					if list_match ${value}
> ${INTEGRITY}; then
> +						log WARNING
> "${value} is already in the list of integrety hashes of this policy"
> +					else
> +						list_append
> INTEGRITY ${value}
> +					fi
> +				fi
> +				;;
> +		esac
> +		shift
> +	done
> +
> +	vpn_security_policies_write_config_key ${name} "INTEGRITY"
> ${INTEGRITY}
> +}

Likewise.

> +
> +vpn_security_policies_key_exchange() {
> +	# This function parses the parameters for the 'key-exchange' 
> command
> +	local name=${1}
> +	local value=${2}
> +	# Check if we get only one argument after compression <name>
> +	if [ ! $# -eq 2 ] || ! isoneof value "ikev1" "ikev2"; then
> +		log ERROR "You can pass only one parameter after
> key-exchange and this one must be 'ikev1' or 'ikev2'"
> +		return ${EXIT_ERROR}
> +	fi

Should we accept values like "IKEv2", too?

> +
> +	vpn_security_policies_write_config_key "${name}"
> "KEY_EXCHANGE" "${value}"
> +}
> +
> +vpn_security_policies_lifetime(){
> +	# This function parses the parameters for the 'lifetime'
> command
> +	local name=${1}
> +	local value=${2}
> +	# Check if we get only one argument after compression <name>
> +	if [ ! $# -eq 2 ] || ! isinteger value || [ ${value} -le 0
> ]; then
> +		log ERROR "You can pass only one parameter after
> liftime and this one must be an valid integer greater zero"
> +		return ${EXIT_ERROR}
> +	fi
> +
> +	vpn_security_policies_write_config_key "${name}" "LIFETIME"
> "${value}"
> +}

This one should accept values like "1h". There is a function for it.

> +
> +vpn_security_policies_pfs(){
> +	# This function parses the parameters for the 'pfs' command
> +	local name=${1}
> +	local value=${2}
> +	# Check if we get only one argument after compression <name>
> +	if [ ! $# -eq 2 ] || ! isbool value; then
> +		log ERROR "You can pass only one parameter after pfs
> and this one must be 'on' or 'off'"
> +		return ${EXIT_ERROR}
> +	fi
> +
> +	vpn_security_policies_write_config_key "${name}" "PFS"
> "${value}"
> +}

See above.

> +
> +vpn_security_policies_check_name() {
> +	# This function checks if a vpn security policy name is
> valid
> +	# Allowed are only A-Za-z0-9
> +	assert [ $# -eq 1 ]
> +	local name=${1}
> +	[[ ${name} =~ [[:alnum:]] ]]

This only checks if there is at least one alphanumerical character in
the string. But it does not exclusively check for them I think.

> +}
> +
> +vpn_security_policies_new() {
> +	# Function that creates based on the paramters one ore more
> new vpn security policies
> +	local name
> +	for name in $@; do
> +		if vpn_security_policy_exists ${name}; then
> +			log ERROR "The vpn security policy ${name}
> does already exist"
> +			continue
> +		fi
> +
> +		if vpn_security_policies_check_name ${name}; then
> +			log ERROR "'${name}' contains illegal
> characters. Allowed are only A-Za-z0-9 are allowed"
> +			continue
> +		fi
> +
> +		log DEBUG "Creating vpn security policy ${name}"
> +		cp "$(vpn_security_policies_path "system")"
> "$(vpn_security_policies_path ${name})"

Don't use cp.

> +	done
> +
> +}

What happens when I create a new policy called "system"?

> +
> +vpn_security_policies_destroy() {
> +	# Function that deletes based on the passed parameters one
> ore more vpn security policies
> +	local name
> +	for name in $@; do
> +		if ! vpn_security_policy_exists ${name}; then
> +			log ERROR "The vpn security policy ${name}
> does not exist"
> +			continue
> +		fi
> +
> +		if vpn_security_policies_check_readonly ${name};
> then
> +			log ERROR "The vpn security policy ${name}
> is readonly and can thats why not deleted"
> +			continue
> +		fi
> +
> +		log DEBUG "Deleting vpn security policy ${name}"
> +		rm -f $(vpn_security_policies_path ${name})
> +	done
> +}

This is overall good work. See the comments above and go through them
one by one and please send an updated version of the patch when ever
you are ready.

Best,
-Michael
  
Michael Tremer July 15, 2017, 3:50 a.m. UTC | #2
Hi,

On Fri, 2017-07-14 at 14:04 +0200, Jonatan Schlag wrote:
> 
> 
> Am Fr, 14. Jul, 2017 um 1:24 schrieb Michael Tremer <michael.tremer@i
> pfire.org>:
> > Hi,
> > 
> > On Thu, 2017-07-13 at 20:33 +0200, Jonatan Schlag wrote:
> >  For further explanation see:
> >  http://wiki.ipfire.org/devel/network/security-policies
> >  
> >  Signed-off-by: Jonatan Schlag <jonatan.schlag@ipfire.org>
> >  ---
> >   src/functions/functions.vpn-security-policies | 437
> >  ++++++++++++++++++++++++++
> >   1 file changed, 437 insertions(+)
> >   create mode 100644 src/functions/functions.vpn-security-policies
> >  
> >  diff --git a/src/functions/functions.vpn-security-policies
> >  b/src/functions/functions.vpn-security-policies
> >  new file mode 100644
> >  index 0000000..9e24e4f
> >  --- /dev/null
> >  +++ b/src/functions/functions.vpn-security-policies
> >  @@ -0,0 +1,437 @@
> >  +#!/bin/bash
> >  +#################################################################
> > ###
> >  ###########
> >  +#                                                                
> >    
> >            #
> >  +# IPFire.org - A linux based
> >  firewall                                         #
> >  +# Copyright (C) 2013  IPFire Network Development
> >  Team                         #
> >  +#                                                                
> >    
> >            #
> > 
> > It's 2017 :)
> > 
> >  +# This program is free software: you can redistribute it and/or
> >  modify        #
> >  +# it under the terms of the GNU General Public License as
> > published
> >  by        #
> >  +# the Free Software Foundation, either version 3 of the License,
> >  or           #
> >  +# (at your option) any later
> >  version.                                         #
> >  +#                                                                
> >    
> >            #
> >  +# This program is distributed in the hope that it will be
> >  useful,             #
> >  +# but WITHOUT ANY WARRANTY; without even the implied warranty
> >  of              #
> >  +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
> >  the               #
> >  +# GNU General Public License for more
> >  details.                                #
> >  +#                                                                
> >    
> >            #
> >  +# You should have received a copy of the GNU General Public
> >  License           #
> >  +# along with this program.  If not, see <http://www.gnu.org/licen
> > ses
> >  />.       #
> >  +#                                                                
> >    
> >            #
> >  +#################################################################
> > ###
> >  ###########
> >  +
> >  +VPN_SECURITY_POLICIES_CONFIG_SETTINGS="CIPHER COMPRESSION
> > GROUP_TYPE
> >  INTEGRITY KEY_EXCHANGE LIFETIME PFS"
> >  +VPN_SECURITY_POLICIES_READONLY="system"
> >  +
> >  +VPN_SUPPORTED_CIPHERS="AES192 AES256 AES128"
> >  +VPN_SUPPORTED_INTEGRITY="SHA512 SHA256 SHA128"
> >  +VPN_SUPPORTED_GROUP_TYPES="MODP4096 MODP8192"
> > 
> > Obviously these need to be extended. But this will suit as a proof-
> > of-
> > concept. Just please order stuff alphabetically. In this case it is
> > best to do it descending.
> > 
> >  +vpn_security_policies_check_readonly() {
> >  +	# This functions checks if a policy is readonly
> >  +	# returns true when yes and false when no
> >  +
> >  +	if isoneof name ${VPN_SECURITY_POLICIES_READONLY}; then
> >  +		return ${EXIT_TRUE}
> >  +	else
> >  +		return ${EXIT_FALSE}
> >  +	fi
> >  +}
> > 
> > Ok.
> > 
> >  +vpn_security_policies_write_config() {
> >  +	# This function writes all values to a via ${name}
> >  specificated vpn security policy configuration file
> >  +	assert [ $# -ge 1 ]
> >  +
> >  +	local name="${1}"
> >  +
> >  +	if ! vpn_security_policy_exists ${name}; then
> >  +		log ERROR "No such policy: ${name}"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> > 
> > I think you need to improve some of the error messages. A sentence
> > is
> > usually good. The shorter the sweeter. Not sure if "no such policy"
> > is
> > helpful when they are actually called security policies. So, don't
> > go
> > too short.
> > 
> >  +
> >  +	if vpn_security_policies_check_readonly ${name}; then
> >  +		log ERROR "${name} is a readable only vpn
> > security
> >  policy"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> > 
> > Here I would suggest: "The system security policy cannot be
> > changed"
> > 
> >  +
> >  +	local args
> >  +	list_append args ${VPN_SECURITY_POLICIES_CONFIG_SETTINGS}
> > 
> > No need to call list_append here. Settings args to
> > VPN_SECURITY_POLICIES_CONFIG_SETTINGS is enough. You just just pass
> > it
> > to settings_write without using $args.
> > 
> >  +
> >  +	local path="$(vpn_security_policies_path ${name})"
> >  +	if [ ! -w ${path} ]; then
> >  +		log ERROR "${path} is not writeable"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +	settings_write "${path}" ${args}
> >  +	# TODO everytime we successfully write a config we should
> >  call some trigger to take the changes into effect
> >  +}
> > 
> > Space between the settings_write and comments, please.
> > 
> > What happens when the settings could not be written?
> > 
> >  +vpn_security_policies_write_config_key() {
> >  +	# This funtion writes the value for one key to a via
> > ${name}
> >  specificated vpn security policy configuration file
> >  +	assert [ $# -ge 3 ]
> >  +	local name=${1}
> >  +
> >  +	if ! vpn_security_policy_exists ${name}; then
> >  +		log ERROR "No such policy: ${name}"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +	local key=${2}
> >  +	shift 2
> >  +	local value="$@"
> >  +	log DEBUG "Set '${key}' to new value '${value}'"
> > 
> > Please group the argument parsing together at the top and then test
> > if
> > the policy exists. This is hard to read.
> > 
> > Also the logging message is a bit random since it doesn't mention
> > the
> > security policy here.
> > 
> >  +	# Read the config settings
> >  +	if ! vpn_security_policies_read_config ${name}; then
> >  +		return ${EXIT_ERROR}
> >  +	fi
> > 
> > This will overwrite any global variables. So you will need to
> > call
> > 
> >   local VPN_SECURITY_POLICIES_CONFIG_SETTINGS
> > 
> > before you read the settings from file.
> > 
> >  +	# Set the key to a new value
> >  +	eval "${key}=\"${value}\""
> > 
> > Never ever use eval. This will allow someone to pass shell commands
> > that may get executed. At no time ever we would want that.
> 
> I was aware of the problems eval can result, but we use it at some
> other functions, so I thought i would be ok.
> Anyway already changed.
> 
> > There is a function called assign() that takes care of assigning a
> > variable.
> > 
> >  +	if ! vpn_security_policies_write_config ${name}; then
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +	return ${EXIT_TRUE}
> >  +
> >  +}
> >  +
> >  +vpn_security_policies_read_config() {
> >  +	# Reads one or more keys out of a settings file or all if
> > no
> >  key is provided.
> >  +	assert [ $# -ge 1 ]
> >  +
> >  +	local name="${1}"
> >  +	shift 1
> >  +
> >  +	if ! vpn_security_policy_exists ${name}; then
> >  +		log ERROR "No such policy: ${name}"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +
> >  +	local args
> >  +	if [ $# -eq 0 ] && [ -n
> >  "${VPN_SECURITY_POLICIES_CONFIG_SETTINGS}" ]; then
> >  +		list_append args
> >  ${VPN_SECURITY_POLICIES_CONFIG_SETTINGS}
> >  +	else
> >  +		list_append args $@
> >  +	fi
> >  +
> >  +	local path="$(vpn_security_policies_path ${name})"
> >  +	if [ ! -r ${path} ]; then
> >  +		log ERROR "${path} is not readable"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> > 
> > Why do you need to do this every time? Doesn't settings_read do
> > this?
> > 
> >  +
> >  +	settings_read "${path}" ${args}
> >  +}
> >  +
> >  +vpn_security_policies_path() {
> >  +	# Returns the path to a the configuration fora given name
> >  +	assert [ $# -eq 1 ]
> >  +	local name=${1}
> >  +
> >  +	if vpn_security_policies_check_readonly ${name}; then
> >  +		echo "/usr/share/network/vpn/security-
> >  policies/${name}"
> >  +	else
> >  +		echo "/etc/network/vpn/security-policies/${name}"
> >  +	fi
> >  +}
> > 
> > Hard-coded paths :(
> > 
> >  +
> >  +vpn_security_policies_show() {
> >  +	# Print the content of a vpn security policy
> > configuration
> >  file in a nice way
> >  +	assert [ $# -eq 1 ]
> >  +	local name=${1}
> > 
> > Here you will need to do the local variable initialization before
> > you
> > red this in - as above.
> > 
> >  +	# Break if read fails
> >  +	if ! vpn_security_policies_read_config ${name}; then
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +	cli_print_fmt1 0 "Security Policy: ${name}"
> >  +	cli_space
> >  +
> >  +	# This could be done in a loop but a loop is much more
> >  complicated
> >  +	# because we print 'Group Types' but the variable is
> > named
> >  'GROUP_TYPES'
> >  +	cli_print_fmt1 1 "Ciphers:"
> >  +	cli_print_fmt1 2 "${CIPHER}"
> >  +	cli_space
> > 
> > This does not show any broken ciphers as broken. But we could add
> > this
> > later as well. This patch is quite huge already.
> > 
> >  +	cli_print_fmt1 1 "Integrity:"
> >  +	cli_print_fmt1 2 "${INTEGRITY}"
> >  +	cli_space
> >  +	cli_print_fmt1 1 "Group Types:"
> >  +	cli_print_fmt1 2 "${GROUP_TYPE}"
> >  +	cli_space
> >  +
> >  +	cli_print_fmt1 1 "Key exchange:" "${KEY_EXCHANGE}"
> > 
> > Capital E in Exchange
> > 
> >  +	cli_print_fmt1 1 "Key Lifetime in seconds:" "${LIFETIME}"
> > 
> > The lifetime should be formatted nicely because seconds are hard to
> > understand. There is a function that formats these nicely.
> > 
> >  +	if enabled PFS; then
> >  +		cli_print_fmt1 1 "Perfect Forward Secrecy:"
> >  "enabled"
> >  +	else
> >  +		cli_print_fmt1 1 "Perfect Forward Secrecy:"
> >  "disabled"
> >  +	fi
> >  +	cli_space
> >  +	if enabled COMPRESSION; then
> >  +		cli_print_fmt1 1 "Compression:" "enabled"
> >  +	else
> >  +		cli_print_fmt1 1 "Compression:" "disabled"
> >  +	fi
> >  +	cli_space
> >  +}
> >  +
> >  +vpn_security_policy_exists() {
> >  +	# This function checks if a vpn security policy exists
> >  +	# Returns True when yes and false when not
> >  +	assert [ $# -eq 1 ]
> >  +	local name=${1}
> >  +
> >  +	local path=$(vpn_security_policies_path ${name})
> >  +	[ -f ${path} ]
> >  +}
> >  +
> >  +
> >  +vpn_security_policies_cipher(){
> >  +	# This function parses the parameters for the 'cipher'
> >  command
> >  +	local name=${1}
> >  +	shift
> >  +
> >  +	if [ $# -eq 0 ]; then
> >  +		log ERROR "You must pass at least one value after
> >  cipher"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> > 
> > local CIPHER
> > 
> >  +	if ! vpn_security_policies_read_config ${name} "CIPHER";
> >  then
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +	# Remove duplicated entries to proceed the list safely
> >  +	CIPHER="$(list_unique ${CIPHER})"
> >  +
> >  +	while [ $# -gt 0 ]; do
> >  +		case "${1}" in
> >  +			-*)
> >  +				value=${1#-}
> >  +				# Check if the cipher is in the
> > list
> >  of ciphers and
> >  +				# check if the list has after
> >  removing this cipher at least one valid value
> >  +				if list_match ${value} ${CIPHER}
> > &&
> >  [ $(list_length ${CIPHER}) -gt 1 ]; then
> >  +					list_remove CIPHER
> > ${value}
> >  +				else
> >  +					log ERROR "Can not remove
> >  ${value} from the list of Ciphers because ${value} is not in the
> > list
> >  or the list would be empty after removing this cipher"
> >  +				fi
> > 
> > There is no return here which ends the function on this error.
> > 
> > I think you should check this after the loop to support things
> > like:
> > 
> > -3DES +AES256
> > 
> > This would crash if 3DES is the last cipher but the user does not
> > intend to remove all ciphers.
> > 
> >  +				;;
> >  +			+*)
> >  +				value=${1#+}
> >  +				# Check if the Ciphers is in the
> >  list of supported ciphers.
> >  +				if ! isoneof value
> >  ${VPN_SUPPORTED_CIPHERS}; then
> >  +					log ERROR "${value} is
> > not a
> >  supported cipher and can thats why not added to the list of
> > ciphers"
> >  +				else
> >  +					if list_match ${value}
> >  ${CIPHER}; then
> >  +						log WARNING
> >  "${value} is already in the list of ciphers of this policy"
> >  +					else
> >  +						list_append
> > CIPHER
> >  ${value}
> >  +					fi
> >  +				fi
> >  +				;;
> >  +		esac
> >  +		shift
> >  +	done
> >  +
> >  +	vpn_security_policies_write_config_key ${name} "CIPHER"
> >  ${CIPHER}
> > 
> > Are there no errors to catch here?
> > 
> >  +}
> >  +
> >  +vpn_security_policies_compression(){
> >  +	# This function parses the parameters for the
> > 'compression'
> >  command
> >  +	local name=${1}
> >  +	local value=${2}
> >  +	# Check if we get only one argument after compression
> > <name>
> >  +	if [ ! $# -eq 2 ] || ! isbool value; then
> >  +		log ERROR "You can pass only one parameter after
> >  compression and this one must be 'on' or 'off'"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> > 
> > Probably not the nicest handling in case of no argument or so. This
> > also suggests two values but actually more would be accepted as
> > well.
> 
> I added a comment but just for clarification, sure more values are
> allowed. But I see no sense in print 1 0 on off true false yes no,
> because it does not make things more clear it makes them even worse.
> From my point of view on and off are the most understandable phrases,
> so I print just this two but other will works too.
> 
> And something like "All boolean values are accepted" is too implicit.

No, that is not better to show all possible values.

But what could help is saying what is not possible. For example:

"Invalid argument"

> >  +
> >  +	vpn_security_policies_write_config_key "${name}"
> >  "COMPRESSION" "${value}"
> >  +}
> >  +
> >  +vpn_security_policies_group_type(){
> >  +	# This function parses the parameters for the 'group-
> > type'
> >  command.
> >  +	local name=${1}
> >  +	shift
> >  +
> >  +	if [ $# -eq 0 ]; then
> >  +		log ERROR "You must pass at least one value after
> >  group-type"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +	if ! vpn_security_policies_read_config ${name}
> > "GROUP_TYPE";
> >  then
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +	# Remove duplicated entries to proceed the list safely
> >  +	GROUP_TYPE="$(list_unique ${GROUP_TYPE})"
> >  +
> >  +	while [ $# -gt 0 ]; do
> >  +		case "${1}" in
> >  +			-*)
> >  +				value=${1#-}
> >  +				# Check if the group type is in
> > the
> >  list of group types and
> >  +				# check if the list has after
> >  removing this group type at leatst one valid value
> >  +				if list_match ${value}
> > ${GROUP_TYPE}
> >  && [ $(list_length ${GROUP_TYPE}) -gt 1 ]; then
> >  +					list_remove GROUP_TYPE
> >  ${value}
> >  +				else
> >  +					log ERROR "Can not remove
> >  ${value} from the list of group types because ${value} is not in
> > the
> >  list or the list would be empty after removing this group type"
> >  +				fi
> >  +				;;
> >  +			+*)
> >  +				value=${1#+}
> >  +				# Check if the group type is in
> > the
> >  list of supported group types.
> >  +				if ! isoneof value
> >  ${VPN_SUPPORTED_GROUP_TYPES}; then
> >  +					log ERROR "${value} is
> > not a
> >  supported group type and can thats why not added to the list of
> > group
> >  types"
> >  +				else
> >  +					if list_match ${value}
> >  ${GROUP_TYPE}; then
> >  +						log WARNING
> >  "${value} is already in the list of group-types of this policy"
> >  +					else
> >  +						list_append
> >  GROUP_TYPES ${value}
> >  +					fi
> >  +				fi
> >  +				;;
> >  +		esac
> >  +		shift
> >  +	done
> >  +
> >  +	vpn_security_policies_write_config_key ${name}
> > "GROUP_TYPE"
> >  ${GROUP_TYPE}
> >  +}
> > 
> > Same as above.
> > 
> >  +
> >  +vpn_security_policies_integrity(){
> >  +	# This function parses the parameters for the 'integrity'
> >  command
> >  +	local name=${1}
> >  +	shift
> >  +
> >  +	if [ $# -eq 0 ]; then
> >  +		log ERROR "You must pass at least one value after
> >  integrity"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +	if ! vpn_security_policies_read_config ${name}
> > "INTEGRITY";
> >  then
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +	# Remove duplicated entries to proceed the list safely
> >  +	INTEGRITY="$(list_unique ${INTEGRITY})"
> >  +
> >  +	while [ $# -gt 0 ]; do
> >  +		case "${1}" in
> >  +			-*)
> >  +				value=${1#-}
> >  +				# Check if the integrity hash is
> > in
> >  the list of integrity hashes and
> >  +				# check if the list has after
> >  removing this  integrity hash at least one valid value
> >  +				if list_match ${value}
> > ${INTEGRITY}
> >  && [ $(list_length ${INTEGRITY}) -gt 1 ]; then
> >  +					list_remove INTEGRITY
> >  ${value}
> >  +				else
> >  +					log ERROR "Can not remove
> >  ${value} from the list of integrity hashes because ${value} is not
> > in
> >  the list or the list would be empty after removing this integrity
> >  hash"
> >  +				fi
> >  +				;;
> >  +			+*)
> >  +				value=${1#+}
> >  +				# Check if the Ciphers is in the
> >  list of supported integrity hashes.
> >  +				if ! isoneof value
> >  ${VPN_SUPPORTED_INTEGRITY}; then
> >  +					log ERROR "${value} is
> > not a
> >  supported integrity hashes and can thats why not added to the list
> > of
> >  integrity hashes"
> >  +				else
> >  +					if list_match ${value}
> >  ${INTEGRITY}; then
> >  +						log WARNING
> >  "${value} is already in the list of integrety hashes of this
> > policy"
> >  +					else
> >  +						list_append
> >  INTEGRITY ${value}
> >  +					fi
> >  +				fi
> >  +				;;
> >  +		esac
> >  +		shift
> >  +	done
> >  +
> >  +	vpn_security_policies_write_config_key ${name}
> > "INTEGRITY"
> >  ${INTEGRITY}
> >  +}
> > 
> > Likewise.
> > 
> >  +
> >  +vpn_security_policies_key_exchange() {
> >  +	# This function parses the parameters for the 'key-
> > exchange' 
> >  command
> >  +	local name=${1}
> >  +	local value=${2}
> >  +	# Check if we get only one argument after compression
> > <name>
> >  +	if [ ! $# -eq 2 ] || ! isoneof value "ikev1" "ikev2";
> > then
> >  +		log ERROR "You can pass only one parameter after
> >  key-exchange and this one must be 'ikev1' or 'ikev2'"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> > 
> > Should we accept values like "IKEv2", too?
> > 
> >  +
> >  +	vpn_security_policies_write_config_key "${name}"
> >  "KEY_EXCHANGE" "${value}"
> >  +}
> >  +
> >  +vpn_security_policies_lifetime(){
> >  +	# This function parses the parameters for the 'lifetime'
> >  command
> >  +	local name=${1}
> >  +	local value=${2}
> >  +	# Check if we get only one argument after compression
> > <name>
> >  +	if [ ! $# -eq 2 ] || ! isinteger value || [ ${value} -le
> > 0
> >  ]; then
> >  +		log ERROR "You can pass only one parameter after
> >  liftime and this one must be an valid integer greater zero"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +	vpn_security_policies_write_config_key "${name}"
> > "LIFETIME"
> >  "${value}"
> >  +}
> > 
> > This one should accept values like "1h". There is a function for
> > it.
> > 
> >  +
> >  +vpn_security_policies_pfs(){
> >  +	# This function parses the parameters for the 'pfs'
> > command
> >  +	local name=${1}
> >  +	local value=${2}
> >  +	# Check if we get only one argument after compression
> > <name>
> >  +	if [ ! $# -eq 2 ] || ! isbool value; then
> >  +		log ERROR "You can pass only one parameter after
> > pfs
> >  and this one must be 'on' or 'off'"
> >  +		return ${EXIT_ERROR}
> >  +	fi
> >  +
> >  +	vpn_security_policies_write_config_key "${name}" "PFS"
> >  "${value}"
> >  +}
> > 
> > See above.
> > 
> >  +
> >  +vpn_security_policies_check_name() {
> >  +	# This function checks if a vpn security policy name is
> >  valid
> >  +	# Allowed are only A-Za-z0-9
> >  +	assert [ $# -eq 1 ]
> >  +	local name=${1}
> >  +	[[ ${name} =~ [[:alnum:]] ]]
> > 
> > This only checks if there is at least one alphanumerical character
> > in
> > the string. But it does not exclusively check for them I think.
> > 
> >  +}
> >  +
> >  +vpn_security_policies_new() {
> >  +	# Function that creates based on the paramters one ore
> > more
> >  new vpn security policies
> >  +	local name
> >  +	for name in $@; do
> >  +		if vpn_security_policy_exists ${name}; then
> >  +			log ERROR "The vpn security policy
> > ${name}
> >  does already exist"
> >  +			continue
> >  +		fi
> >  +
> >  +		if vpn_security_policies_check_name ${name}; then
> >  +			log ERROR "'${name}' contains illegal
> >  characters. Allowed are only A-Za-z0-9 are allowed"
> >  +			continue
> >  +		fi
> >  +
> >  +		log DEBUG "Creating vpn security policy ${name}"
> >  +		cp "$(vpn_security_policies_path "system")"
> >  "$(vpn_security_policies_path ${name})"
> > 
> > Don't use cp.
> 
> Why not not and what would be a better solution?

Just read the stuff and then write it again using the shell. That
avoids forking the cp process. The other reason is that cp is not very
predictable sometimes. If a directory exists for the target, it would
then place the file into that directory. That is not what we want.

You could maybe write a function called copy() in the util functions
file that looks like this:

copy() {
	local src=${1}
	local dst=${2}

	local data=$(fread ${src})

	fwrite ${dst} "${data}"
}

Obviously this is not smart for gigabytes of data, but this is good
enough to copy a configuration file of a few kilobytes.

Best,
-Michael

> 
> Best,
> Jonatan
> 
> > 
> >  +	done
> >  +
> >  +}
> > 
> > What happens when I create a new policy called "system"?
> > 
> >  +
> >  +vpn_security_policies_destroy() {
> >  +	# Function that deletes based on the passed parameters
> > one
> >  ore more vpn security policies
> >  +	local name
> >  +	for name in $@; do
> >  +		if ! vpn_security_policy_exists ${name}; then
> >  +			log ERROR "The vpn security policy
> > ${name}
> >  does not exist"
> >  +			continue
> >  +		fi
> >  +
> >  +		if vpn_security_policies_check_readonly ${name};
> >  then
> >  +			log ERROR "The vpn security policy
> > ${name}
> >  is readonly and can thats why not deleted"
> >  +			continue
> >  +		fi
> >  +
> >  +		log DEBUG "Deleting vpn security policy ${name}"
> >  +		rm -f $(vpn_security_policies_path ${name})
> >  +	done
> >  +}
> > 
> > This is overall good work. See the comments above and go through
> > them
> > one by one and please send an updated version of the patch when
> > ever
> > you are ready.
> > 
> > Best,
> > -Michael
> > 
> > 
> >