Hi,

03.04.2011 22:42, ruslan usifov wrote:
>     You need some tuning from both sides.
>     First, (at least some versions of) ietd needs to be blocked (-j DROP)
>     with iptables on restarts. That means, you should block all incoming and
>     outgoing packets (later is more important) before ietd stop and unblock
>     all after it starts. I use home-brew stateful RA for this, which blocks
>     (DROP) all traffic to/from VIP in slave mode and passes it to a later
>     decision (no -j) in master mode.
> 
> 
> 
> Thanks for reply, But how to implemet this from pacemaker. Or i must to
> modify inet.d scripts?

You can try attached.
This was a first trial to write RA... It may contain some wrong logic,
especially with scores, but it works for me.
It is intended to be colocated with IP-address resource:
colocation col1 inf: FW:Master ip
order ord1 inf: ip:start FW:promote

Vladislav
#!/bin/bash
#
#
#               OCF Resource Agent compliant script
#
# Copyright (c) 2010 Vitalativ S.R.O.,
# Copyright (c) 2010 Vladislav Bogdanov
#
#                                        All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Further, this software is distributed without any warranty that it is
# free of the rightful claim of any third person regarding infringement
# or the like.  Any license provided herein, whether implied or
# otherwise, applies only to this software file.  Patent licenses, if
# any, provided herein do not apply to combinations of this program with
# other software, or any other product whatsoever.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
#
#

# OCF instance parameters
#       OCF_RESKEY_vip
#       OCF_RESKEY_CRM_meta_clone_max
#       OCF_RESKEY_CRM_meta_clone_node_max
#       OCF_RESKEY_CRM_meta_master_max
#       OCF_RESKEY_CRM_meta_master_node_max

#######################################################################
# Initialization:

: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs

export LANG=C LANGUAGE=C LC_ALL=C

# Defaults according to "Configuration 1.0 Explained",
# "Multi-state resource configuration options"
: ${OCF_RESKEY_CRM_meta_clone_node_max=1}
: ${OCF_RESKEY_CRM_meta_master_max=1}
: ${OCF_RESKEY_CRM_meta_master_node_max=1}

OCF_RESKEY_allow_action_default="accept"
: ${OCF_RESKEY_allow_action=${OCF_RESKEY_allow_action_default}}

OCF_RESKEY_deny_action_default="drop"
: ${OCF_RESKEY_deny_action=${OCF_RESKEY_deny_action_default}}

#######################################################################
# for debugging this RA
DEBUG_LOG_DIR=/tmp/vipfw.ocf.ra.debug
DEBUG_LOG=$DEBUG_LOG_DIR/log
USE_DEBUG_LOG=false
ls_stat_is_dir_0700_root() {
        set -- $(command ls -ldn "$1" 2>/dev/null);
        [[ $1/$3 = drwx?-??-?/0 ]]
}
# try to avoid symlink vuln.
if ls_stat_is_dir_0700_root $DEBUG_LOG_DIR &&
   [[ -w "$DEBUG_LOG" && ! -L "$DEBUG_LOG" ]]
then
        USE_DEBUG_LOG=true
        exec 9>>"$DEBUG_LOG"
        date >&9
        echo "$*" >&9
        env | grep OCF_ | sort >&9
else
        exec 9>/dev/null
fi
# end of debugging aid
#######################################################################

meta_data() {
        cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="VIPfirewall">
<version>0.1</version>

<longdesc lang="en">
This resource agent manages firewall rules for Virtual IP addresses
as a master/slave resource, blocking all traffic to/from VIP in Slave state and 
allowing
it in a Master state. Main purpose of this to be written is support for 
seamless IET iSCSI
target resource (backed by DRBD) migration.
</longdesc>

<shortdesc lang="en">Master/Slave OCF Resource Agent for Virtual IP address 
blocking by OS firewall</shortdesc>

<parameters>
<parameter name="vip" unique="1" required="1">
<longdesc lang="en">
Virtual IP address (CIDR prefixes are accepted too).
</longdesc>
<shortdesc lang="en">ip addresses</shortdesc>
<content type="string"/>
</parameter>
<parameter name="allow_action" required="0" unique="0">
<longdesc lang="en">
How should iptables be configured on promote. Must be one of "accept" or "pass",
first one will explicitely permit traffic to/from VIP, second will pass it 
further to chains.
Default is "accept".
</longdesc>
<shortdesc lang="en">iptables action on promote</shortdesc>
<content type="string" default="${OCF_RESKEY_allow_action_default}"/>
</parameter>
<parameter name="deny_action" required="0" unique="0">
<longdesc lang="en">
How should iptables be configured on start/demote. Must be one of "drop" or 
"reject",
first one will silently drop traffic to/from VIP, second will result in ICMP 
"port-unreachable" messages.
Default is "drop".
</longdesc>
<shortdesc lang="en">iptables action on start/demote</shortdesc>
<content type="string" default="${OCF_RESKEY_deny_action_default}"/>
</parameter>

</parameters>

<actions>
<action name="start"   timeout="5" />
<action name="promote"   timeout="5" />
<action name="demote"   timeout="5" />
<action name="notify"   timeout="5" />
<action name="stop"    timeout="5" />
<action name="monitor" depth="0"  timeout="5" interval="5" start-delay="1m" 
role="Slave" />
<action name="monitor" depth="0"  timeout="5" interval="5" start-delay="1m" 
role="Master" />
<action name="meta-data"  timeout="5" />
<action name="validate-all"  timeout="5" />
</actions>
</resource-agent>
END
}

do_cmd() {
        # Run a command, return its exit code, capture any output, and log
        # everything if appropriate.
        local cmd="$*" cmd_out ret
        ocf_log debug "$OCF_RESOURCE_INSTANCE ($OCF_RESKEY_vip): Calling $cmd"
        cmd_out=$( "$@" )
        ret=$?

        if [ $ret != 0 ]; then
                ocf_log err "$OCF_RESOURCE_INSTANCE ($OCF_RESKEY_vip): Called 
$cmd"
                ocf_log err "$OCF_RESOURCE_INSTANCE ($OCF_RESKEY_vip): Exit 
code $ret"
                ocf_log err "$OCF_RESOURCE_INSTANCE ($OCF_RESKEY_vip): Command 
output: $cmd_out"
        else
                ocf_log debug "$OCF_RESOURCE_INSTANCE ($OCF_RESKEY_vip): Exit 
code $ret"
                ocf_log debug "$OCF_RESOURCE_INSTANCE ($OCF_RESKEY_vip): 
Command output: $cmd_out"
        fi

        echo "$cmd_out"

        return $ret
}

vipfw_set_master_score() {
        # Use quiet mode (-Q) to quench logging. Actual score updates
        # will get logged by attrd anyway
        do_cmd ${HA_SBIN_DIR}/crm_master -Q -l reboot -v $1
}

vipfw_remove_master_score() {
        do_cmd ${HA_SBIN_DIR}/crm_master -l reboot -D
}

vipfw_update_master_score() {
        # We have no any preferences yet
        # FIXME: Shouldn't we check for presence of VIP locally?
        # VAL: not sure.
        vipfw_set_master_score  1000

        return $OCF_SUCCESS
}

is_firewall_enabled() {
        # VAL: pros and cons with using dedicated TABLE?
        test -f /proc/net/ip_tables_names
}

#######################################################################


vipfw_usage() {
        echo "\
usage: $0 {start|stop|monitor|validate-all|promote|demote|notify|meta-data}

Expects to have a fully populated OCF RA-compliant environment set."
}

VIPFW_POLICY_UNKNOWN=0
VIPFW_POLICY_ACCEPT=1
VIPFW_POLICY_PASS=2
VIPFW_POLICY_DROP=3
VIPFW_POLICY_REJECT=4

vipfw_get_policy() {
        local policy

        set -- `iptables -nL $1 | \
                grep -E 
"$2[[:space:]]+all[[:space:]]+--[[:space:]]+$3[[:space:]]+$4[[:space:]]+$"| \
                head -n1`
        case $1 in
        ACCEPT)
                policy=$VIPFW_POLICY_ACCEPT
                ;;
        DROP)
                policy=$VIPFW_POLICY_DROP
                ;;
        REJECT)
                policy=$VIPFW_POLICY_REJECT
                ;;
        all) # No target specified and second field is catched instead of first 
one
                policy=$VIPFW_POLICY_PASS
                ;;
        *)
                policy=$VIPFW_POLICY_UNKNOWN
                ;;
        esac
        return $policy
}

vipfw_status() {
        local rc policy_input policy_output
        local catch_regexp="^(ACCEPT|DROP|REJECT|[[:space:]]+)"

        rc=$OCF_NOT_RUNNING

        if ! is_firewall_enabled ; then
                return $rc
        fi

        case $1 in
        non_pass)
                catch_regexp="^(ACCEPT|DROP|REJECT)"
                ;;
        *)
                ;;
        esac
        regexp_vip=${OCF_RESKEY_vip//./\\.} # Prepare dots for regexp
        regexp_vip=${regexp_vip/\/32/} # Strip /32 if any to comply with 
iptables output
        regexp_all="0\.0\.0\.0/0"

        vipfw_get_policy "INPUT" ${catch_regexp} ${regexp_all} ${regexp_vip}
        policy_input=$?

        vipfw_get_policy "OUTPUT" ${catch_regexp} ${regexp_vip} ${regexp_all}
        policy_output=$?

        if (( $policy_input != $policy_output )) ; then
                return $OCF_ERR_GENERIC # ??? FIXME!!!
        fi

        case $policy_input in
        $VIPFW_POLICY_ACCEPT)
                if [ ${OCF_RESKEY_allow_action} == "accept" ] ; then
                        rc=$OCF_RUNNING_MASTER
                fi
                ;;
        $VIPFW_POLICY_PASS)
                if [ ${OCF_RESKEY_allow_action} == "pass" ] ; then
                        # Recursion, check next rule after 'pass' one
                        vipfw_status "non_pass"
                        case $? in
                        $OCF_SUCCESS)
                                # We have 'drop' or' reject' rule right after 
'pass' rule and
                                # that rule is configured by ourselves.
                                # That means that traffic will be blocked.
                                # Fail for now.
                                rc=$OCF_ERR_GENERIC
                                ;;
                        $OCF_NOT_RUNNING)
                                # We didn't find any more rules except our 
'pass' rule.
                                # This means that we are configured correctly, 
independently
                                # of correct or broken default iptables setup.
                                # If traffic is blocked by later rules then 
that's not our fault.
                                rc=$OCF_RUNNING_MASTER
                                ;;
                        $OCF_ERR_GENERIC)
                                # Hmmm. Something is really broken.
                                rc=$OCF_ERR_GENERIC
                                ;;
                        esac
                fi
                ;;
        $VIPFW_POLICY_DROP)
                if [ ${OCF_RESKEY_deny_action} == "drop" ] ; then
                        rc=$OCF_SUCCESS
                fi
                ;;
        $VIPFW_POLICY_REJECT)
                if [ ${OCF_RESKEY_deny_action} == "reject" ] ; then
                        rc=$OCF_SUCCESS
                fi
                ;;
        *)
                ;;
        esac

        return $rc
}

vipfw_monitor() {
        local status

        vipfw_status
        status=$?

        vipfw_update_master_score

        return $status
}

# This will return after first successful operation
do_iptables() {
        local rc=1
        local count=0

        # Call iptables from within a loop, because iptables is known to fail
        # on concurrent operations
        # iptables returns 2 on command line params error, stop in that case too
        # VAL: you are paranoid...
        until (( rc != 1 )) || (( count > 10 )) ; do # We are in bash, right?
                # First try to be quiet
                # Yes, no debug. But no flood in logs too.
                if [ $USE_DEBUG_LOG == true ] ; then
                        do_cmd $IPTABLES "$@"
                else
                        $IPTABLES "$@" >/dev/null 2>&1
                fi
                rc=$?
                (( count++ ))
        done
        if (( count > 10 )) ; then
                # Try once more and log error
                do_cmd $IPTABLES "$@"
                rc=$?
        fi
        return $rc
}

# This will run command in loop until first error
do_iptables_all() {
        local rc=0
        local count=0

        # Call iptables from within a loop, because iptables is known to fail
        # on concurrent operations
        # iptables returns 2 on command line params error
        until (( rc != 0 )) ; do
                # First try to be quiet
                # Yes, no debug. But no flood in logs too.
                if [ $USE_DEBUG_LOG == true ] ; then
                        do_cmd $IPTABLES "$@"
                else
                        $IPTABLES "$@" >/dev/null 2>&1
                fi
                rc=$?
                (( count++ ))
        done
        if (( count > 1 )) ; then # We succeded at least once
                return 0
        else
                return $rc
        fi
}

vipfw_op() {
        local op op1
        local target target1
        local rc_in
        local rc_out
        local rc

        case $1 in
        # It is not safe to use "-R" because the only way to replace rule is by 
its rule number
        # But, that number can change between read and replace operations. So I 
use insert/delete.
        add|block|unblock)
                # We should add our rules very early in list to avoid default 
setup with stateful firewall
                # break things
                op="-I"
                target="-j DROP"
                case $1 in
                unblock)
                        if [ ${OCF_RESKEY_allow_action} == "pass" ] ; then
                                target=""
                        else
                                target="-j ACCEPT"
                        fi
                        ;;
                *)
                        ;;
                esac

                do_iptables $op INPUT  -d $OCF_RESKEY_vip $target
                rc_in=$?
                do_iptables $op OUTPUT -s $OCF_RESKEY_vip $target
                rc_out=$?

                if (( $rc_in )) || (( $rc_out )) ; then
                        rc=1
                else
                        rc=0
                fi
                ;;
        *)
                ;;
        esac

        case $1 in
        block|unblock|delete)
                # be safe on deletion: delete by rule spec, not by rule number
                op="-D"
                target="-j DROP"
                case $1 in
                block)
                        # It is not safe to use "-R" because the only way to 
replace rule is by its rule number
                        # But, that number can change between read and replace 
operations
                        if [ ${OCF_RESKEY_allow_action} == "pass" ] ; then
                                target=""
                        else
                                target="-j ACCEPT"
                        fi
                        ;;
                *)
                        ;;
                esac
                # Delete all matching rules (they can remain from previous runs)
                # We are not interested in result, because this is only a 
cleanup.
                # FIXME: Nope, that's wrong. We MUST ensure that all DROP rules 
are deleted if
                # we use 'pass' mode'
                do_iptables_all $op INPUT  -d $OCF_RESKEY_vip $target
                do_iptables_all $op OUTPUT -s $OCF_RESKEY_vip $target
                ;;
        *)
                ;;
        esac

        return $rc
}

vipfw_start() {
        local rc
        local status
        local first_try=true

        rc=$OCF_ERR_GENERIC

        if ! is_firewall_enabled; then
                        ocf_log err "Firewall is not started, unable to block 
VIPs."$'\n';
                        return $OCF_ERR_INSTALLED
        fi

        # Keep trying to bring up the resource;
        # wait for the CRM to time us out if this fails
        while :; do
                vipfw_status
                status=$?
                case "$status" in
                $OCF_SUCCESS)
                        rc=$OCF_SUCCESS
                        break
                        ;;
                $OCF_NOT_RUNNING)
                        vipfw_op add
                        ;;
                $OCF_RUNNING_MASTER)
                        ocf_log warn "$OCF_RESKEY_vip already promoted, 
demoting."
                        vipfw_op block
                        ;;
                $OCF_ERR_GENERIC)
                        vipfw_op block
                        ;;
                esac
                $first_try || sleep 1
                first_try=false
        done
        # in case someone does not configure monitor,
        # we must at least call it once after start.
        vipfw_update_master_score

        return $rc
}

vipfw_promote() {
        local rc
        local status
        local first_try=true

        rc=$OCF_ERR_GENERIC

        # Keep trying to promote the resource;
        # wait for the CRM to time us out if this fails
        while :; do
                vipfw_status
                status=$?
                case "$status" in
                $OCF_SUCCESS)
                        vipfw_op unblock
                        ;;
                $OCF_NOT_RUNNING)
                        ocf_log error "Trying to promote a resource that was 
not started"
                        break
                        ;;
                $OCF_RUNNING_MASTER)
                        rc=$OCF_SUCCESS
                        break
                        ;;
                $OCF_ERR_GENERIC)
                        vipfw_op unblock
                        ;;
                esac
                $first_try || sleep 1
                first_try=false
        done

        return $rc
}

vipfw_demote() {
        local rc
        local status
        local first_try=true

        rc=$OCF_ERR_GENERIC

        # Keep trying to demote the resource;
        # wait for the CRM to time us out if this fails
        while :; do
                vipfw_status
                status=$?
                case "$status" in
                $OCF_SUCCESS)
                        rc=$OCF_SUCCESS
                        break
                        ;;
                $OCF_NOT_RUNNING)
                        ocf_log error "Trying to demote a resource that was not 
started"
                        break
                        ;;
                $OCF_RUNNING_MASTER)
                        vipfw_op block
                        ;;
                $OCF_ERR_GENERIC)
                        vipfw_op block
                        ;;
                esac
                $first_try || sleep 1
                first_try=false
        done

        return $rc
}

vipfw_stop() {
        local rc=$OCF_ERR_GENERIC
        local first_try=true

        # Keep trying to bring down the resource;
        # wait for the CRM to time us out if this fails
        while :; do
                vipfw_status
                status=$?
                case "$status" in
                $OCF_SUCCESS)
                        vipfw_op delete
                        ;;
                $OCF_NOT_RUNNING)
                        rc=$OCF_SUCCESS
                        break
                        ;;
                $OCF_RUNNING_MASTER)
                        ocf_log warn "$OCF_RESKEY_vip still Primary, demoting."
                        vipfw_op block
                        ;;
                $OCF_ERR_GENERIC)
                        vipfw_op block
                        vipfw_op delete
                        ;;
                esac
                $first_try || sleep 1
                first_try=false
        done

        # do not let old master scores laying around.
        # they may confuse crm if this node was set to standby.
        vipfw_remove_master_score

        return $rc
}


vipfw_notify() {
        local n_type=$OCF_RESKEY_CRM_meta_notify_type
        local n_op=$OCF_RESKEY_CRM_meta_notify_operation

        # FIXME: Nothing to do?
        return $OCF_SUCCESS
}

# "macro" to be able to give useful error messages
# on clone resource configuration error.
meta_expect()
{
        local what=$1 whatvar=OCF_RESKEY_CRM_meta_${1//-/_} op=$2 expect=$3
        local val=${!whatvar}
        if [[ -n $val ]]; then
                # [, not [[, or it won't work ;)
                [ $val $op $expect ] && return
        fi
        ocf_log err "meta parameter misconfigured, expected $what $op $expect, 
but found ${val:-unset}."
        exit $OCF_ERR_CONFIGURED
}

ls_stat_is_block_maj_147() {
        set -- $(command ls -L -l "$1" 2>/dev/null)
        [[ $1 = b* ]] && [[ $5 == 147,* ]]
}

check_crm_feature_set()
{
        set -- ${OCF_RESKEY_crm_feature_set//[!0-9]/ }
        local a=${1:-0} b=${2:-0} c=${3:-0}
        
        (( a > 3 )) ||
        (( a == 3 && b > 0 )) ||
        (( a == 3 && b == 0 && c > 0 )) ||
        ocf_log warn "You may be disappointed: This RA is intended for 
pacemaker 1.0 or better!"
}

vipfw_validate_all() {
        check_binary $IPTABLES
        # XXX I really take cibadmin, sed, grep, etc. for granted.

        check_crm_feature_set

        # Check clone and M/S options.
        meta_expect clone-max -le 2
        meta_expect clone-node-max = 1
        meta_expect master-node-max = 1
        meta_expect master-max = 1

        case "$OCF_RESKEY_vip" in
        "")
                ocf_log err "No Virtual IP address specified!"
                return $OCF_ERR_CONFIGURED
                ;;
        *[!./0-9]*)
                ocf_log err "IP address must only contain [./0-9]"
                return $OCF_ERR_CONFIGURED
        esac

        case "${OCF_RESKEY_allow_action}" in
        "pass"|"accept")
                ;;
        *)
                ocf_log err "allow_action must be one of \"accept\" or \"pass\""
                return $OCF_ERR_CONFIGURED
                ;;
        esac

        case "${OCF_RESKEY_deny_action}" in
        "drop"|"reject")
                ;;
        *)
                ocf_log err "deny_action must be one of \"drop\" or \"reject\""
                return $OCF_ERR_CONFIGURED
                ;;
        esac

        return $OCF_SUCCESS
}

#######################################################################

if [ $# != 1 ]; then
        vipfw_usage
        exit $OCF_ERR_ARGS
fi

# if $__OCF_ACTION = monitor, but meta_interval not set,
# this is a "probe". we could change behaviour.
: ${OCF_RESKEY_CRM_meta_interval=0}

case $__OCF_ACTION in
meta-data)
        meta_data
        exit $OCF_SUCCESS
        ;;
usage)
        vipfw_usage
        exit $OCF_SUCCESS
esac

if $USE_DEBUG_LOG ; then
        exec 2>&9
        set -x
fi

# Everything except usage and meta-data must pass the validate test
vipfw_validate_all || exit

case $__OCF_ACTION in
start)
        vipfw_start
        ;;
stop)
        vipfw_stop
        ;;
notify)
        vipfw_notify
        ;;
promote)
        vipfw_promote
        ;;
demote)
        vipfw_demote
        ;;
status)
        vipfw_status
        ;;
monitor)
        vipfw_monitor
        ;;
validate-all)
        ;;
*)
        vipfw_usage
        exit $OCF_ERR_UNIMPLEMENTED
esac
# exit code is the exit code (return code) of the last command (shell function)
_______________________________________________
Pacemaker mailing list: Pacemaker@oss.clusterlabs.org
http://oss.clusterlabs.org/mailman/listinfo/pacemaker

Project Home: http://www.clusterlabs.org
Getting started: http://www.clusterlabs.org/doc/Cluster_from_Scratch.pdf
Bugs: http://developerbugs.linux-foundation.org/enter_bug.cgi?product=Pacemaker

Reply via email to