diff options
| author | Santosh Kumar Pradhan <spradhan@redhat.com> | 2014-04-09 10:19:43 +0530 | 
|---|---|---|
| committer | Vijay Bellur <vbellur@redhat.com> | 2014-04-22 23:40:26 -0700 | 
| commit | 00e247ee44067f2b3e7ca5f7e6dc2f7934c97181 (patch) | |
| tree | 43868ec642fae37fcb013e8c2f66659bf9d7b3cc | |
| parent | b6cc23204f1941184cb08ec3d84beecd2d06fd91 (diff) | |
gNFS: Support wildcard in RPC auth allow/reject
RFE: Support wildcard in "nfs.rpc-auth-allow" and
"nfs.rpc-auth-reject". e.g.
  *.redhat.com
  192.168.1[1-5].*
  192.168.1[1-5].*, *.redhat.com, 192.168.21.9
  Along with wildcard, support for subnetwork or IP range e.g.
  192.168.10.23/24
The option will be validated for following categories:
1) Anonymous i.e. "*"
2) Wildcard pattern i.e. string containing any ('*', '?', '[')
3) IPv4 address
4) IPv6 address
5) FQDN
6) subnetwork or IPv4 range
Currently this does not support IPv6 subnetwork.
Change-Id: Iac8caf5e490c8174d61111dad47fd547d4f67bf4
BUG: 1086097
Signed-off-by: Santosh Kumar Pradhan <spradhan@redhat.com>
Reviewed-on: http://review.gluster.org/7485
Reviewed-by: Poornima G <pgurusid@redhat.com>
Reviewed-by: Harshavardhana <harsha@harshavardhana.net>
Tested-by: Gluster Build System <jenkins@build.gluster.com>
Reviewed-by: Vijay Bellur <vbellur@redhat.com>
| -rw-r--r-- | libglusterfs/src/common-utils.c | 123 | ||||
| -rw-r--r-- | libglusterfs/src/common-utils.h | 5 | ||||
| -rw-r--r-- | libglusterfs/src/options.c | 40 | ||||
| -rw-r--r-- | libglusterfs/src/options.h | 1 | ||||
| -rw-r--r-- | rpc/rpc-lib/src/rpcsvc.c | 91 | ||||
| -rwxr-xr-x | tests/bugs/bug-822830.t | 24 | ||||
| -rw-r--r-- | xlators/nfs/server/src/nfs.c | 8 | 
7 files changed, 281 insertions, 11 deletions
diff --git a/libglusterfs/src/common-utils.c b/libglusterfs/src/common-utils.c index 80d9d294053..b7d06d9251e 100644 --- a/libglusterfs/src/common-utils.c +++ b/libglusterfs/src/common-utils.c @@ -1858,6 +1858,70 @@ out:          return ret;  } +/** + * valid_ipv4_subnetwork() takes the pattern and checks if it contains + * a valid ipv4 subnetwork pattern i.e. xx.xx.xx.xx/n. IPv4 address + * part (xx.xx.xx.xx) and mask bits lengh part (n). The mask bits lengh + * must be in 0-32 range (ipv4 addr is 32 bit). The pattern must be + * in this format. + * + * Returns _gf_true if both IP addr and mask bits len are valid + *         _gf_false otherwise. + */ +gf_boolean_t +valid_ipv4_subnetwork (const char *address) +{ +        char         *slash     = NULL; +        char         *paddr     = NULL; +        char         *endptr    = NULL; +        long         prefixlen  = -1; +        gf_boolean_t retv       = _gf_true; + +        if (address == NULL) { +                gf_log_callingfn (THIS->name, GF_LOG_WARNING, +                                              "argument invalid"); +                return _gf_false; +        } + +        paddr = gf_strdup (address); +        if (paddr == NULL) /* ENOMEM */ +                return _gf_false; + +        /* +         * INVALID: If '/' is not present OR +         *          Nothing specified after '/' +         */ +        slash = strchr(paddr, '/'); +        if ((slash == NULL) || (slash[1] == '\0')) { +                gf_log_callingfn (THIS->name, GF_LOG_WARNING, +                                  "Invalid IPv4 subnetwork format"); +                retv = _gf_false; +                goto out; +        } + +        *slash = '\0'; +        retv = valid_ipv4_address (paddr, strlen(paddr), _gf_false); +        if (retv == _gf_false) { +                gf_log_callingfn (THIS->name, GF_LOG_WARNING, +                                  "Invalid IPv4 subnetwork address"); +                goto out; +        } + +        prefixlen = strtol (slash + 1, &endptr, 10); +        if ((errno != 0) || (*endptr != '\0') || +            (prefixlen < 0) || (prefixlen > 32)) { +                gf_log_callingfn (THIS->name, GF_LOG_WARNING, +                                  "Invalid IPv4 subnetwork mask"); +                retv = _gf_false; +                goto out; +        } + +        retv = _gf_true; +out: +        GF_FREE (paddr); +        return retv; +} +  char  valid_ipv6_address (char *address, int length, gf_boolean_t wildcard_acc)  { @@ -1939,6 +2003,65 @@ out:  }  /** + * valid_mount_auth_address - Validate the rpc-auth.addr.allow/reject pattern + * + * @param address - Pattern to be validated + * + * @return _gf_true if "address" is "*" (anonymous) 'OR' + *                  if "address" is valid FQDN or valid IPv4/6 address 'OR' + *                  if "address" contains wildcard chars e.g. "'*' or '?' or '['" + *                  if "address" is valid ipv4 subnet pattern (xx.xx.xx.xx/n) + *         _gf_false otherwise + * + * + * NB: If the user/admin set for wildcard pattern, then it does not have + *     to be validated. Make it similar to the way exportfs (kNFS) works. + */ +gf_boolean_t +valid_mount_auth_address (char *address) +{ +        int    length = 0; +        char   *cp    = NULL; + +        /* 1. Check for "NULL and empty string */ +        if ((address == NULL) || (address[0] == '\0')){ +                gf_log_callingfn (THIS->name, +                                  GF_LOG_WARNING, "argument invalid"); +                return _gf_false; +        } + +        /* 2. Check for Anonymous */ +        if (strcmp(address, "*") == 0) +                return _gf_true; + +        for (cp = address; *cp; cp++) { +                /* 3. Check for wildcard pattern */ +                if (*cp == '*' || *cp == '?' || *cp == '[') { +                        return _gf_true; +                } + +                /* +                 * 4. check for IPv4 subnetwork i.e. xx.xx.xx.xx/n +                 * TODO: check for IPv6 subnetwork +                 * NB: Wildcard must not be mixed with subnetwork. +                 */ +                if (*cp == '/') { +                        return valid_ipv4_subnetwork (address); +                } +        } + +        /* 5. Check for v4/v6 IP addr and FQDN/hostname */ +        length = strlen (address); +        if ((valid_ipv4_address (address, length, _gf_false)) || +            (valid_ipv6_address (address, length, _gf_false)) || +            (valid_host_name (address, length))) { +                return _gf_true; +        } + +        return _gf_false; +} + +/**   * gf_sock_union_equal_addr - check if two given gf_sock_unions have same addr   *   * @param a - first sock union diff --git a/libglusterfs/src/common-utils.h b/libglusterfs/src/common-utils.h index e17029dbaf9..3a58a933121 100644 --- a/libglusterfs/src/common-utils.h +++ b/libglusterfs/src/common-utils.h @@ -580,9 +580,8 @@ char valid_host_name (char *address, int length);  char valid_ipv4_address (char *address, int length, gf_boolean_t wildcard_acc);  char valid_ipv6_address (char *address, int length, gf_boolean_t wildcard_acc);  char valid_internet_address (char *address, gf_boolean_t wildcard_acc); -char valid_ipv4_wildcard_check (char *address); -char valid_ipv6_wildcard_check (char *address); -char valid_wildcard_internet_address (char *address); +gf_boolean_t valid_mount_auth_address (char *address); +gf_boolean_t valid_ipv4_subnetwork (const char *address);  gf_boolean_t gf_sock_union_equal_addr (union gf_sock_union *a,                                         union gf_sock_union *b); diff --git a/libglusterfs/src/options.c b/libglusterfs/src/options.c index 31e5a681d11..a0881b4ad91 100644 --- a/libglusterfs/src/options.c +++ b/libglusterfs/src/options.c @@ -574,6 +574,45 @@ out:          return ret;  } +static int +xlator_option_validate_mntauth (xlator_t *xl, const char *key, +                                const char *value, volume_option_t *opt, +                                char **op_errstr) +{ +        int          ret = -1; +        char         *dup_val = NULL; +        char         *addr_tok = NULL; +        char         *save_ptr = NULL; +        char         errstr[4096] = {0,}; + +        dup_val = gf_strdup (value); +        if (!dup_val) +                goto out; + +        addr_tok = strtok_r (dup_val, ",", &save_ptr); +        if (addr_tok == NULL) +                goto out; +        while (addr_tok) { +                if (!valid_mount_auth_address (addr_tok)) +                        goto out; + +                addr_tok = strtok_r (NULL, ",", &save_ptr); +        } +        ret = 0; + +out: +        if (ret) { +                snprintf (errstr, sizeof (errstr), "option %s %s: '%s' is not " +                "a valid mount-auth-address", key, value, value); +                gf_log (xl->name, GF_LOG_ERROR, "%s", errstr); +                if (op_errstr) +                        *op_errstr = gf_strdup (errstr); +        } +        GF_FREE (dup_val); + +        return ret; +} +  /*XXX: the rules to validate are as per block-size required for stripe xlator */  static int  gf_validate_size (const char *sizestr, volume_option_t *opt) @@ -744,6 +783,7 @@ xlator_option_validate (xlator_t *xl, char *key, char *value,                  xlator_option_validate_priority_list,                  [GF_OPTION_TYPE_SIZE_LIST]   = xlator_option_validate_size_list,                  [GF_OPTION_TYPE_ANY]         = xlator_option_validate_any, +                [GF_OPTION_TYPE_CLIENT_AUTH_ADDR] = xlator_option_validate_mntauth,                  [GF_OPTION_TYPE_MAX]         = NULL,          }; diff --git a/libglusterfs/src/options.h b/libglusterfs/src/options.h index 62f4ee92e91..134cc360293 100644 --- a/libglusterfs/src/options.h +++ b/libglusterfs/src/options.h @@ -38,6 +38,7 @@ typedef enum {          GF_OPTION_TYPE_INTERNET_ADDRESS_LIST,          GF_OPTION_TYPE_PRIORITY_LIST,          GF_OPTION_TYPE_SIZE_LIST, +        GF_OPTION_TYPE_CLIENT_AUTH_ADDR,          GF_OPTION_TYPE_MAX,  } volume_option_type_t; diff --git a/rpc/rpc-lib/src/rpcsvc.c b/rpc/rpc-lib/src/rpcsvc.c index 8be64c18aa2..34ee6f21b49 100644 --- a/rpc/rpc-lib/src/rpcsvc.c +++ b/rpc/rpc-lib/src/rpcsvc.c @@ -59,6 +59,9 @@ int  rpcsvc_notify (rpc_transport_t *trans, void *mydata,                 rpc_transport_event_t event, void *data, ...); +static int +match_subnet_v4 (const char *addrtok, const char *ipaddr); +  rpcsvc_notify_wrapper_t *  rpcsvc_notify_wrapper_alloc (void)  { @@ -2181,6 +2184,13 @@ rpcsvc_transport_peer_check_search (dict_t *options, char *pattern,                                  goto err;                  } +                /* Compare IPv4 subnetwork */ +                if (strchr (addrtok, '/')) { +                        ret = match_subnet_v4 (addrtok, ip); +                        if (ret == 0) +                                goto err; +                } +                  addrtok = strtok_r (NULL, ",", &svptr);          } @@ -2327,8 +2337,20 @@ rpcsvc_auth_check (rpcsvc_t *svc, char *volname,          ret = dict_get_str (options, srchstr, &reject_str);          GF_FREE (srchstr); -        if (reject_str == NULL && !strcmp ("*", allow_str)) -                return RPCSVC_AUTH_ACCEPT; + +        /* +         * If "reject_str" is being set as '*' (anonymous), then NFS-server +         * would reject everything. If the "reject_str" is not set and +         * "allow_str" is set as '*' (anonymous), then NFS-server would +         * accept mount requests from all clients. +         */ +        if (reject_str != NULL) { +                if (!strcmp ("*", reject_str)) +                        return RPCSVC_AUTH_REJECT; +        } else { +                if (!strcmp ("*", allow_str)) +                        return RPCSVC_AUTH_ACCEPT; +        }          /* Non-default rule, authenticate */          if (!get_host_name (client_ip, &ip)) @@ -2461,6 +2483,71 @@ out:          return addrstr;  } +/* + * match_subnet_v4() takes subnetwork address pattern and checks + * if the target IPv4 address has the same network address with + * the help of network mask. + * + * Returns 0 for SUCCESS and -1 otherwise. + * + * NB: Validation of subnetwork address pattern is not required + *     as it's already being done at the time of CLI SET. + */ +static int +match_subnet_v4 (const char *addrtok, const char *ipaddr) +{ +        char                 *slash     = NULL; +        char                 *netaddr   = NULL; +        long                  prefixlen = -1; +        int                   ret       = -1; +        uint32_t              shift     = 0; +        struct sockaddr_in    sin1      = {0, }; +        struct sockaddr_in    sin2      = {0, }; +        struct sockaddr_in    mask      = {0, }; + +        /* Copy the input */ +        netaddr = gf_strdup (addrtok); +        if (netaddr == NULL) /* ENOMEM */ +                goto out; + +        /* Find the network socket addr of target */ +        if (inet_pton (AF_INET, ipaddr, &sin1.sin_addr) == 0) +                goto out; + +        /* Find the network socket addr of subnet pattern */ +        slash = strchr (netaddr, '/'); +        *slash = '\0'; +        if (inet_pton (AF_INET, netaddr, &sin2.sin_addr) == 0) +                goto out; + +        /* +         * Find the network mask in network byte order. +         * NB: 32 : Max len of IPv4 address. +         */ +        prefixlen = atoi (slash + 1); +        shift = 32 - (uint32_t)prefixlen; +        mask.sin_addr.s_addr = htonl ((uint32_t)~0 << shift); + +        /* +         * Check if both have same network address. +         * Extract the network address from the IP addr by applying the +         * network mask. If they match, return SUCCESS. i.e. +         * +         * (x == y) <=> (x ^ y == 0) +         * (x & y) ^ (x & z) <=> x & (y ^ z) +         * +         * ((ip1 & mask) == (ip2 & mask)) <=> ((mask & (ip1 ^ ip2)) == 0) +         */ +        if (((mask.sin_addr.s_addr) & +             (sin1.sin_addr.s_addr ^ sin2.sin_addr.s_addr)) != 0) +                goto out; + +        ret = 0; /* SUCCESS */ +out: +        GF_FREE (netaddr); +        return ret; +} +  rpcsvc_actor_t gluster_dump_actors[] = {          [GF_DUMP_NULL]      = {"NULL",     GF_DUMP_NULL,     NULL,        NULL, 0, DRC_NA}, diff --git a/tests/bugs/bug-822830.t b/tests/bugs/bug-822830.t index 000d99f03cc..b7a5704cdba 100755 --- a/tests/bugs/bug-822830.t +++ b/tests/bugs/bug-822830.t @@ -18,18 +18,38 @@ EXPECT 'Created' volinfo_field $V0 'Status';  TEST $CLI volume start $V0;  EXPECT 'Started' volinfo_field $V0 'Status'; -## Setting nfs.rpc-auth-reject as 192.*..* -TEST ! $CLI volume set $V0 nfs.rpc-auth-reject 192.*..* +## Setting nfs.rpc-auth-reject as 192.{}.1.2 +TEST ! $CLI volume set $V0 nfs.rpc-auth-reject 192.{}.1.2  EXPECT '' volinfo_field $V0 'nfs.rpc-auth-reject';  # Setting nfs.rpc-auth-allow as a.a.  TEST ! $CLI volume set $V0 nfs.rpc-auth-allow a.a.  EXPECT '' volinfo_field $V0 'nfs.rpc-auth-allow'; +## Setting nfs.rpc-auth-reject as 192.*..* +TEST $CLI volume set $V0 nfs.rpc-auth-reject 192.*..* +EXPECT '192.*..*' volinfo_field $V0 'nfs.rpc-auth-reject'; +  # Setting nfs.rpc-auth-allow as a.a  TEST $CLI volume set $V0 nfs.rpc-auth-allow a.a  EXPECT 'a.a' volinfo_field $V0 'nfs.rpc-auth-allow'; +# Setting nfs.rpc-auth-allow as *.redhat.com +TEST $CLI volume set $V0 nfs.rpc-auth-allow *.redhat.com +EXPECT '\*.redhat.com' volinfo_field $V0 'nfs.rpc-auth-allow'; + +# Setting nfs.rpc-auth-allow as 192.168.10.[1-5] +TEST $CLI volume set $V0 nfs.rpc-auth-allow 192.168.10.[1-5] +EXPECT '192.168.10.\[1-5]' volinfo_field $V0 'nfs.rpc-auth-allow'; + +# Setting nfs.rpc-auth-allow as 192.168.70.? +TEST $CLI volume set $V0 nfs.rpc-auth-allow 192.168.70.? +EXPECT '192.168.70.?' volinfo_field $V0 'nfs.rpc-auth-allow'; + +# Setting nfs.rpc-auth-reject as 192.168.10.5/16 +TEST $CLI volume set $V0 nfs.rpc-auth-reject 192.168.10.5/16 +EXPECT '192.168.10.5/16' volinfo_field $V0 'nfs.rpc-auth-reject'; +  ## Setting nfs.rpc-auth-reject as 192.*.*  TEST $CLI volume set $V0 nfs.rpc-auth-reject 192.*.*  EXPECT '192.*.*' volinfo_field $V0 'nfs.rpc-auth-reject'; diff --git a/xlators/nfs/server/src/nfs.c b/xlators/nfs/server/src/nfs.c index 04cf030dc12..d962663bafa 100644 --- a/xlators/nfs/server/src/nfs.c +++ b/xlators/nfs/server/src/nfs.c @@ -1665,7 +1665,7 @@ struct volume_options options[] = {                           "unrecognized option warnings."          },          { .key  = {"rpc-auth.addr.allow"}, -          .type = GF_OPTION_TYPE_INTERNET_ADDRESS_LIST, +          .type = GF_OPTION_TYPE_CLIENT_AUTH_ADDR,            .default_value = "all",            .description = "Allow a comma separated list of addresses and/or"                           " hostnames to connect to the server. By default, all" @@ -1673,7 +1673,7 @@ struct volume_options options[] = {                           "define a general rule for all exported volumes."          },          { .key  = {"rpc-auth.addr.reject"}, -          .type = GF_OPTION_TYPE_INTERNET_ADDRESS_LIST, +          .type = GF_OPTION_TYPE_CLIENT_AUTH_ADDR,            .default_value = "none",            .description = "Reject a comma separated list of addresses and/or"                           " hostnames from connecting to the server. By default," @@ -1681,7 +1681,7 @@ struct volume_options options[] = {                           "define a general rule for all exported volumes."          },          { .key  = {"rpc-auth.addr.*.allow"}, -          .type = GF_OPTION_TYPE_INTERNET_ADDRESS_LIST, +          .type = GF_OPTION_TYPE_CLIENT_AUTH_ADDR,            .default_value = "all",            .description = "Allow a comma separated list of addresses and/or"                           " hostnames to connect to the server. By default, all" @@ -1689,7 +1689,7 @@ struct volume_options options[] = {                           "define a rule for a specific exported volume."          },          { .key  = {"rpc-auth.addr.*.reject"}, -          .type = GF_OPTION_TYPE_INTERNET_ADDRESS_LIST, +          .type = GF_OPTION_TYPE_CLIENT_AUTH_ADDR,            .default_value = "none",            .description = "Reject a comma separated list of addresses and/or"                           " hostnames from connecting to the server. By default,"  | 
