Git Product home page Git Product logo

sdc-cloudapi's Introduction

sdc-cloudapi

This repository is part of the Triton DataCenter project. See the contribution guidelines and general documentation at the main Triton project page.

CloudAPI is the HTTP API that customers use to interact with SmartDataCenter.

Adding CloudAPI to SDC

cloudapi is not created by default during SDC setup. You can create it by running in the root global zone (either inside COAL or on an SDC headnode server):

sdcadm post-setup cloudapi

Development

A CloudAPI server should be running in a cloudapi zone after running the sdcadm command above. Alternatively, a more manual approach is:

git clone [email protected]:TritonDataCenter/sdc-cloudapi.git
cd sdc-cloudapi
git submodule update --init
make all
node main.js -f ./etc/cloudapi.config.json

Configuration file.

The configuration file ./etc/cloudapi.cfg needs to be created before the CloudAPI server can run. Consequently, this file is also required in order to run the test suite.

There is an example cloudapi.coal.cfg file in the repository, with the default values every required variable should take if we were running the tests on our development machine, which has access to a COAL setup.

Please remember that if you're trying to modify this file within an actual cloudapi zone, the config file is created - and automatically updated - by the config-agent service using the template file also in this repo (sapi_manifests/cloudapi/template) and the SAPI configuration values.

Testing

Before testing, you need to create an environment the tests can operate in, on the headnode you're using for tests. Assuming that you'll be testing using COAL's headnode, and that you've already created a cloudapi zone on that headnode, the easiest way to prepare the headnode for CloudAPI testing will be running the following from the global zone:

/zones/`vmadm lookup -1 alias=cloudapi0`/root/opt/smartdc/cloudapi/tools/coal-setup.sh

This script will hack DAPI for headnode provisioning, update imgapi to allow local custom images, and install some services, images packages required for thorough testing.

Once you've completed this process, run the following from within the cloudapi zone:

./test/runtests

The runtests script does a safety check for a canary file before attempting to run any tests, to prevent unwanted writes. If the canary is not found, runtests will let you know; create (e.g. using touch) the file and rerun runtests.

To run a specific test file, and not the entire test suite, use the -f flag with runtests. For example:

./test/runtests -f nics.test.js

This will run test/nics.test.js. If you want to run multiple test files, -f effectively globs too:

./test/runtests -f machines

This will run all the machines* test files in test/.

It is possible to run the test suite outside of a cloudapi zone, but this is an increasingly unbeaten path. If you are so inclined, then ensure that ./etc/cloudapi.cfg is set up appropriately and execute:

make test

But your life will be simpler if you stick to a cloudapi zone and use the runtests script; various config settings and environmental flags are set by runtests automatically.

Image management

If you want to test image management using COAL, the faster approach is to run the aforementioned coal-setup.sh script from the global zone. Amongst other things, local image management setup will be completed.

Testing RBAC

This section assumes your setup includes a reasonably recent version of UFDS and Mahi. If you're not sure, please update both to latest.

There's an utility script intented to speed up ENV setup for RBAC testing in your local setup. Assuming you want to test RBAC in COAL, you'll need to:

  • Setup CloudAPI zone (see above).

  • Add the account account and the user user, both with password joypass123, and both of them using the SSH key ~/.ssh/id_rsa.pub:

    ./tools/create-account.sh headnode

  • Clone v7.3 or later branch of node-smartdc from https://github.com/TritonDataCenter/node-smartdc

  • Assuming you want to test in COAL, you should have the following ENV vars setup to operate as the account owner:

      SDC_URL=https://<IP of cloudapi zone>
      SDC_TESTING=true
      SDC_ACCOUNT=account
      SDC_KEY_ID=`ssh-keygen -l -E md5 -f ~/.ssh/id_rsa.pub| awk '{print $2}' | tr -d '\n'|cut -c 5-`
    

And, in order to operate as the account user instead, you just need to add the ENV var:

    SDC_USER=user

given we already created both with the same SSH key/fingerprint.

If you want to also test machines creation and the associated actions, you'll need to hack the setup the same way we do for testing:

/zones/`vmadm lookup -1 \
alias=cloudapi0`/root/opt/smartdc/cloudapi/tools/coal-setup.sh

For more information on RBAC you can check CloudAPI docs and the Access Control User Guide.

How CloudAPI Auth works using RBAC

Roles and Policies are used in CloudAPI to provide access control for accounts' sub users. Authorization for account sub users is always made using HTTP Signature. The following is a brief description of CloudAPI access control process for sub users (all of this assuming account_mgmt feature is enabled and req.version >= 7.2.0):

Identify request resource

CloudAPI identifies the name of the resource for the request. This can be either a collection of resources or an individual one. While this usually matches the request path, it's not always true. For example:

a. ListFirewallRules: Firewal Rules Resource Collection: /:account/fwrules. b. GetFirewallRule: Individual Firewall Rule Resource: /:account/fwrules/:fwruleid c. EnableFirewallRule: The same individual firewall Rule resource than for GetFirewallRule, identified by /:account/fwrules/:fwruleid even when the path for this request would be /:account/fwrules/:fwruleid/enable.

It's to say, for a given individual resource, all actions happening over this resource will share the name which is the path for the main GetResource request. For example, every action listed under the Machines epigraph in CloudAPI docs related to an individual machine will have the same resource, "the machine", identified by /:account/machines/:machineid, even when these actions could be rename machine, enable firewall, add tags, create snapshot, audit ...

Load resource role-tags when exist

Once the name of the resource for the current request has been identified, CloudAPI checks if there are any role-tag associated with the current resource.

(role-tags are just a set of one or more roles associated with the current resource. CloudAPI customers can associate role-tag to resources using the names of the roles they want to provide some kind of access to the resource.)

role-tag loading is done differently depending if the current resource is an individual machine (given machines store role-tag by themselves) or something else. Everything but machines uses UFDS' sdcAccountResource objectclass, which has the following attributes:

dn: resource-uuid=:resource_uuid, uuid=:account_uuid, ou=users, o=smartdc
account: account_uuid
memberrole: [aRoleDN, anotherRoleDN, ...]
name: :resource_name
objectclass: sdcaccountresource
uuid: :resource_uuid

Behind the scences, CloudAPI "translates" the role DNs into their respective role objects.

For machines, given each machine may have a role_tag member in VMAPI, which is an array of roles' UUIDs, CloudAPI does exactly the same regarding role translation from UUID into the collection of role objects.

(Please, note that, in order to be able to use machine role-tag to handle sub-user auth, we need to preload machine loading for all the machine related requests).

In both cases, our request object will have the following properties:

req.resourcename = :resource_name
req.resource = {
    name: req.resourcename,
    account: req.account.uuid,
    roles: [[ {role_object}[ , {role_object}, ... ] ]]
};

Ask MAHI to authorize/deny user access

When CloudAPI detects that the current request is being performed by an account sub-user, it will load the sub-user active roles (i.e. user.default_roles), and will pass those, together with the current resource roles collected into the previous step, to aperture for user authorization. Additionally, the current request path, method and route name are also given to aperture.

What needs to happen for the user to get access to the current resource then?:

a. The user must have at least one of the roles assigned to the resource. b. For these roles, at least one of the policies associated with them must have a rule which allows the current request method for the given route name, for example: CAN get AND head IF route::string = listusers

sdc-cloudapi's People

Contributors

arekinath avatar babelfish avatar bahamas10 avatar bahamat avatar cburroughs avatar ccobine avatar danmcd avatar dyep49 avatar fkuo avatar jasonbking avatar jclulow avatar jemershaw avatar johananlai avatar johnsonnenschein avatar joshwilsdon avatar joyent-automation avatar kusor avatar marsell avatar mcavage avatar nickziv avatar orlandov avatar pfmooney avatar rjloura avatar rmustacc avatar thekvn avatar travispaul avatar trentm avatar twhiteman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sdc-cloudapi's Issues

GetNic Will return "not found" wrongly

While Extending https://github.com/terraform-providers/terraform-provider-triton to accept NetworkObjects I discovered some intermittent behavior here. I created the following test to demonstrate what was occuring.

The following test demonstrates:
A NIC is added to a newly created running Instance.
We begin polling for a state of "running"
We find the NIC that we added with the state of "provisioning" for awhile
The NIC is no longer found for awhile
We find the NIC that we added with the state of "running"

Test:

        inst, _ := c.Instances().Create(context.Background(), &compute.CreateInstanceInput{
                Name:    "Nic-test-5",
                Image:   "3dbbdcca-2eab-11e8-b925-23bf77789921",
                Package: "sample-2G",
        })

        for {
                running, _ := c.Instances().Get(context.Background(), &compute.GetInstanceInput{
                        ID: inst.ID,
                })

                if running.State == "running" {
                        break
                }

                time.Sleep(2 * time.Second)
        }

        nic, err := c.Instances().AddNIC(context.Background(), &compute.AddNICInput{
                InstanceID: inst.ID,
                NetworkObject: compute.NetworkObject{
                        IPv4UUID: "0a63f067-d721-435e-941a-724776d69c91",
                        //IPv4IPs:  []string{"10.1.1.58"},
                },
                //Network: "71a1abac-f003-4b51-ac63-28d37f2ef0af",
        })

        fmt.Println(nic, err)

        for {
                nicrunning, err := c.Instances().GetNIC(context.Background(), &compute.GetNICInput{
                        InstanceID: inst.ID,
                        MAC:        nic.MAC,
                })

                fmt.Println(nicrunning, err)

                if err == nil {
                        if nicrunning.State == "running" {
                                goto X
                                break
                        }
                }

                time.Sleep(1 * time.Second)
        }
X:
}

Result:

arch@archlinux ~/g/s/g/test ❯❯❯ go run main.go                                                                                        ✘ 1
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 provisioning 0a63f067-d721-435e-941a-724776d69c91} <nil>
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
<nil> unable to get machine NIC: ResourceNotFound: nic not found
&{10.1.1.91 90:b8:d0:81:68:21 false 255.255.255.0 10.1.1.1 running 0a63f067-d721-435e-941a-724776d69c91} <nil>

InternalError exceptions when handling user ids in roles

sdc-cloudapi currently uses node-mahi version 2.3.0. This version has a double-callback bug in its getUserById() implementation., which is exposed when using roles that have members identified by id instead of login. mahi client v2.3.1 fixes this.

Update cloudapi to use mahi client 2.3.3, and add tests to cover using "id" instead of "login" when creating or using roles.

CloudAPI should allow provisioning machines with delegated datasets

Unless I'm missing something, the ability to create an instance with a delegated dataset is missing when creating that instance via CloudAPI. In AdminUI, one sees a checkbox "delegate dataset". Certain technologies require datasets to function (manatee comes to mind first), and it is a less than ideal user experience that one cannot use CloudAPI to create such instances.

Similar to #13, this allows end-user tools or separation of duties. One doesn't need to be a Triton administrator to be able to manage instances with delegated dataset.

Retire RC4 cipher suite supported in cloudapi

cloudapi offers RC4 as an algorithm option in it's list of TLS ciphers.

While not urgent RC4 is not considered suitably strong anymore and vulnerable to specific attacks and will be flagged in audits:

http://www.nessus.org/u?217a3666
http://cr.yp.to/talks/2013.03.12/slides.pdf
http://www.isg.rhul.ac.uk/tls/
http://www.imperva.com/docs/HII_Attacking_SSL_when_using_RC4.pdf

The SSL cipher list is configured in the stud configuration file and seems to have been sourced from cloudflare originally. Cloudflare have since updated there cipher list:

Current configuration

# Preferred cipher list taken from
# https://github.com/cloudflare/sslconfig
ciphers = "EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:HIGH:RC4-SHA:!MD5:!aNULL:!PSK"

Configuration as currently used by cloudflare https://github.com/cloudflare/sslconfig/blob/master/conf:

EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;

sub user account running sdc-docker-setup script

I wanted to start a discussion on using triton/docker containers with a Joyent subuser account.

I'm not sure if I should file this report against sdc-docker repo or here at sdc-cloudapi.

As an FYI - I've also read this section of the Triton FAQ:
Does Triton support RBAC?
Yes, on an account level basis. We are evaluating how to best extend RBAC to sub-account users, given current limitations of the Docker API.

I'm guessing the above means, the "owner account" is the only place that triton/docker containers can run right now.

I've created a subuser account using the documentation for creating an administrator role and added a user to it:
sdc-role create --name=administrator

sdc-user create --login=cmosetick/testadmin --password=MyPasswd [email protected]

sdc-role update 9a7e4afa-2946-4a4b-8805-4e5e36240c77 --members=testadmin --default-members=testadmin

I also added a ssh key to the testadmin subuser account, and confirmed that it is there in the web GUI.

I've added the testadmin ssh key to my ssh-agent session.

IMO, there is no way the output of this error message could be accurate:

bash ./sdc-docker-setup.sh -k us-east-3b.api.joyent.com cmosetick/testadmin ~/.ssh/testadmin-joyent                                                   1 ↵
Setting up Docker client for SDC using:
   CloudAPI:        https://us-east-3b.api.joyent.com
   Account:         cmosetick/testadmin
   Key:             /Users/chris/.ssh/testadmin-joyent

If you have a pass phrase on your key, the openssl command will
prompt you for your pass phrase now and again later.

Verifying CloudAPI access.

* * *
sdc-docker-setup.sh: fatal error: invalid credentials
   You must add create the 'cmosetick/testadmin' account and/or add your SSH
   public key (/Users/chris/.ssh/testadmin-joyent.pub) to the
   given SmartDataCenter.

Since the administrator role is supposed to be a 'special admin role', IMO, the setup script should work, or at the very least, a more meaningful error message should be returned to a subuser running the setup script. I do also want to note here that the "Docker" section of my.joyent.com web GUI is missing from this sub users account, which again, is outlined in the current Triton FAQ.

I think adding a special "triton" or "docker" role to the sdc-cloudapi would be a straight forward path to allowing subusers to create containers. Allowing the account owner to specify a corresponding policy with a hard limit of max number of containers, max amount of disk space for all containers would be the most logical start for this.

FYI the ssh key in my test is actually associated with the sub user account as far as I can tell, which would leave the sdc-cloudapi or the Docker API as the hurdle to allowing this to work.

Let me know how I can help out with this.

Machines resize tests failing on COAL

On my COAL instance, the dimensions are insufficient to pass a particular test:

not ok 462 should be equal
  ---
    operator: equal
    expected: 1
    actual:   2
    at: parseResponse (/opt/smartdc/cloudapi/node_modules/restify/lib/clients/json_client.js:91:9)
    stack: |-
      Error: should be equal
          at Test.assert [as _assert] (/opt/smartdc/cloudapi/node_modules/tape/lib/test.js:228:54)
          at Test.bound [as _assert] (/opt/smartdc/cloudapi/node_modules/tape/lib/test.js:80:32)
          at Test.equal (/opt/smartdc/cloudapi/node_modules/tape/lib/test.js:389:10)
          at Test.bound (/opt/smartdc/cloudapi/node_modules/tape/lib/test.js:80:32)
          at /opt/smartdc/cloudapi/test/machines/resize.js:45:19
          at parseResponse (/opt/smartdc/cloudapi/node_modules/restify/lib/clients/json_client.js:91:9)
          at IncomingMessage.done (/opt/smartdc/cloudapi/node_modules/restify/lib/clients/string_client.js:167:13)
          at IncomingMessage.g (events.js:292:16)
          at emitNone (events.js:91:20)
          at IncomingMessage.emit (events.js:185:7)
  ...

The test is expected to fail due to server dimensions, except that in this case it's failing across two dimensions, not the expected one. In this case:

{ code: 'ValidationFailed',
  message: 'Invalid VM update parameters',
  errors:
   [ { field: 'ram',
       code: 'InsufficientCapacity',
       message: 'Required additional RAM (10239744) exceeds the server\'s available RAM (-34953)' },
     { field: 'quota',
       code: 'InsufficientCapacity',
       message: 'Required additional disk (99990) exceeds the server\'s available disk (-1015)' } ] }

The quota error is expected. The RAM is not. It should handle the latter case gracefully too.

Network List should List subnet and address range if admin

arch@9d46dd2d-1178-ce83-cbc4-d396e4a24060 ~ ❯❯❯ triton networks -l
ID                                    NAME               SUBNET            GATEWAY        FABRIC  VLAN  PUBLIC
7a7d3510-c834-431d-9cf7-8e7219f3bb9a  My-Fabric-Network  192.168.128.0/22  192.168.128.1  true    2     false
d86b63bb-3d8b-4abf-94e9-9cad37d190d1  cloudops_dmz_0     -                 -              -       -     true
37a1cc60-ced2-4e59-a62f-1d40525dc8b8  cps_lan            -                 -              -       -     true
0eca2659-28cb-42af-bc45-0b5e4aa134cc  dev01              192.168.20.0/24   192.168.20.1   true    200   false
a14a8828-33ff-4546-96b2-649a7f78ad6f  dev02              192.168.21.0/24   192.168.21.1   true    201   false
68c9cd97-de9f-4108-9fcf-a9ae1dd7840b  sdc_nat            -                 -              -       -     true
6b6f4454-990e-4d19-a071-464e13487aa5  test123            10.20.30.0/24     -              true    2     false
a79485ce-4821-4731-978e-cb0d49df0e54  test4              10.20.31.0/24     -              true    2     false

I'm an admin on cps_lan, and users should know which address range they can provision.

want support for additional "external" NIC tags

Currently the logic which decides which networks are "public" and should be automatically added to instances without any specific networks requested is looking only for the "external" NIC tag.

It would be nice to have a SAPI tweakable for installations where another tag is used (e.g. we use the eait_core tag at UQ for historical reasons).

Malformed user-agent breaks prometheus parser

While debugging why triton-prometheus stated that cloudapi0 was DOWN, with the error: expected equal, got "INVALID"
I noticed a funny user-agent in a malicious POST attempt to cloudapi.

[2021-02-05T23:25:37.426Z]  INFO: cloudapi/audit/97818 on 5d980efc-cd02-4690-a871-5d8b050ec7da: handled: 401 (req_id=7c69147f-c657-4006-a50b-3b698890cb15, audit=true, remoteAddress=172.105.13.165, remotePort=33315, latency=10, route=updateaccount, _audit=true)
    POST /sdk HTTP/1.1
    content-length: 441
    user-agent: "Mozilla/5.0
    host: xx.xx.xx.xx
    x-forwarded-for: 172.105.13.165
    --
    HTTP/1.1 401 Unauthorized
    content-type: application/json
    content-length: 94
    access-control-allow-origin: *
    access-control-allow-headers: Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, Api-Version, Response-Time
    access-control-allow-methods: GET, HEAD, POST, PUT
    access-control-expose-headers: Api-Version, Request-Id, Response-Time
    connection: Keep-Alive
    content-md5: ZHr/q0kH6ganClU5iLN5zA==
    date: Fri, 05 Feb 2021 23:25:37 GMT
    server: cloudapi/9.14.0
    api-version: 9.0.0
    request-id: 7c69147f-c657-4006-a50b-3b698890cb15
    response-time: 10
    --
    InvalidCredentialsError: You must make authenticated requests to use CloudAPI
        at Server.assertAuthenticated (/opt/smartdc/cloudapi/lib/auth.js:404:21)
        at next (/opt/smartdc/cloudapi/node_modules/restify/lib/server.js:912:30)
        at f (/opt/smartdc/cloudapi/node_modules/once/once.js:25:25)
        at Server.tokenAuth (/opt/smartdc/cloudapi/lib/auth.js:305:16)
        at next (/opt/smartdc/cloudapi/node_modules/restify/lib/server.js:912:30)
        at f (/opt/smartdc/cloudapi/node_modules/once/once.js:25:25)
        at Server.signatureAuth (/opt/smartdc/cloudapi/lib/auth.js:133:16)
        at next (/opt/smartdc/cloudapi/node_modules/restify/lib/server.js:912:30)
        at f (/opt/smartdc/cloudapi/node_modules/once/once.js:25:25)
        at Server.preSignedUrl (/opt/smartdc/cloudapi/lib/auth.js:729:9)
    --
    req.timers: {
      "parseAccept": 195,
      "parseAuthorization": 24,
      "parseDate": 44,
      "bunyan": 137,
      "parseQueryString": 40,
      "readBody": 779,
      "parseBody": 136,
      "restifyResponseHeaders": 49,
      "xForwardedFor": 46,
      "setupSDCProxies": 116,
      "accountMgmt": 232,
      "preSignedUrl": 88,
      "signatureAuth": 26,
      "tokenAuth": 23,
      "assertAuthenticated": 1985
    }

Take note of:
user-agent: "Mozilla/5.0

Using promtool check metrics on the cloudapi cmon endpoint brought up this

error while linting: text format parsing error in line 1241: unexpected end of label value %!q(*string=0xc00045fa60)

and gave me the line to look for, where I noticed the user agent with an extra ", thats probably causing the prometheus parser error:

http_requests_completed{route="updateaccount",method="POST",user_agent=""Mozilla/5.0",status_code="401",datacenter="dc-1",instance="5d980efc-cd02-4690-a871-5d8b050ec7da",port="8081",server="ccfd1804-307b-11df-a234-e41f13425a34",service="cloudapi"} 1

listkeys error

After CreateKey or DeleteKey operation,for more than 10 seconds,sdc-listkeys results are repeated in between old and new data.

$ triton key ls
FINGERPRINT NAME
6f:f8:c7:......:e1:03:cd initial-key

wangjh@wangjh-BCC-PC MINGW32 /c/Users/wangjh
$ triton key add --name=test ~/.ssh/github_rsa.pub
Added key "test"

(20:40:a7:......:e1:3c:b4)

wangjh@wangjh-BCC-PC MINGW32 /c/Users/wangjh
$ triton key ls
FINGERPRINT NAME
6f:f8:c7:......:e1:03:cd initial-key
20:40:a7:......:e1:3c:b4 test

wangjh@wangjh-BCC-PC MINGW32 /c/Users/wangjh
$ triton key ls
FINGERPRINT NAME
6f:f8:c7:......:e1:03:cd initial-key

wangjh@wangjh-BCC-PC MINGW32 /c/Users/wangjh
$ triton key ls
FINGERPRINT NAME
6f:f8:c7:......:e1:03:cd initial-key
20:40:a7:......:e1:3c:b4 test

wangjh@wangjh-BCC-PC MINGW32 /c/Users/wangjh
$ triton key ls
FINGERPRINT NAME
6f:f8:c7:......:e1:03:cd initial-key
20:40:a7:......:e1:3c:b4 test

wangjh@wangjh-BCC-PC MINGW32 /c/Users/wangjh
$ triton key ls
FINGERPRINT NAME
6f:f8:c7:......:e1:03:cd initial-key

wangjh@wangjh-BCC-PC MINGW32 /c/Users/wangjh
$ triton key ls
FINGERPRINT NAME
6f:f8:c7:......:e1:03:cd initial-key
20:40:a7:......:e1:3c:b4 test

wangjh@wangjh-BCC-PC MINGW32 /c/Users/wangjh
$ triton key ls
FINGERPRINT NAME
6f:f8:c7:......:e1:03:cd initial-key

This problem exists in node-triton CLI、node-smartdc CLI and CloudAPI.

Useless "InternalError" with no message when attempting to provision on an account with no SSH keys

  • Make a new account, with no SSH keys added
  • Create a sub-user on that account, give it administrator role, add SSH keys to it only
  • Use that sub-user to try to create a VM on the account (eg using sdc-createmachine)

Result:

sdc-createmachine: error (InternalError): Internal Error

In the logs on the cloudapi0 zone:

[2015-02-25T04:22:09.648Z] ERROR: cloudapi/30434 on 6e774737-6cbf-44d6-acb8-35a707866eac: unexpected error (req_id=dd459c70-bca5-11e4-8533-0d6eb058ec29)
    AssertionError: options (object) is required
        at KeyRequiredError.RestError (/opt/smartdc/cloudapi/node_modules/restify/lib/errors/rest_error.js:40:16)
        at new KeyRequiredError (/opt/smartdc/cloudapi/lib/machines.js:62:15)
        at /opt/smartdc/cloudapi/lib/machines.js:805:25
        at /opt/smartdc/cloudapi/node_modules/ufds/node_modules/once/once.js:17:15
        at /opt/smartdc/cloudapi/node_modules/ufds/node_modules/once/once.js:17:15
        at EventEmitter.<anonymous> (/opt/smartdc/cloudapi/node_modules/ufds/lib/index.js:2787:13)
        at EventEmitter.emit (events.js:95:17)
        at _done (/opt/smartdc/cloudapi/node_modules/ufds/node_modules/ldapjs/lib/client/client.js:1133:22)
        at messageCallback (/opt/smartdc/cloudapi/node_modules/ufds/node_modules/ldapjs/lib/client/client.js:1219:16)
        at Parser.onMessage (/opt/smartdc/cloudapi/node_modules/ufds/node_modules/ldapjs/lib/client/client.js:833:14)

It looks like the API for RestError in restify has changed and now requires an options object instead of the arguments that machines.js is giving it. As a result, the useful error message 'At least one SSH key is required to provision' which it's supposed to produce in this case, is being turned into a useless 'Internal Error'.

As an aside, I think loadSSHKeys should be looking at sub-users as well as the account keys, perhaps with a second special role "login" or a policy addition. This also needs to be fixed with smartlogin though, which currently ignores sub-users and roles/policy too.

Owned NonFabric Networks don't show network information

Networks that are owned by a tenant, but aren't fabrics don't display Subnet / Gateway information during triton networks.

/g/i/consul ❯❯❯ tt networks
SHORTID   NAME               SUBNET            GATEWAY        FABRIC  VLAN  PUBLIC
4b609af0  My-Fabric-Network  192.168.128.0/22  192.168.128.1  true    2     false
a0ae9499  consul_demo        192.168.10.0/24   192.168.10.1   true    2     false
b3c03534  home               -                 -              -       -     false
913849ab  nexus              10.0.0.0/24       10.0.0.1       true    20    false
17d7aa50  sdc_nat            -                 -              -       -     true

owner:

[root@headnode (us-east-1) ~]# sdc-napi /networks/b3c03534-0380-43d2-83b6-45a112dcd057
HTTP/1.1 200 OK
Etag: 070215F5
Content-Type: application/json
Content-Length: 370
Date: Thu, 06 Dec 2018 01:42:15 GMT
Server: SmartDataCenter Networking API
x-request-id: 1d788afc-7f5f-4f45-ace1-4a9057027b9b
x-response-time: 65
x-server-name: b5c8e696-cce3-4eba-9163-984bb636c896
Connection: keep-alive

{
  "family": "ipv4",
  "mtu": 1500,
  "nic_tag": "home",
  "name": "home",
  "provision_end_ip": "192.168.2.250",
  "provision_start_ip": "192.168.2.100",
  "subnet": "192.168.2.0/24",
  "uuid": "b3c03534-0380-43d2-83b6-45a112dcd057",
  "vlan_id": 0,
  "resolvers": [
    "192.168.1.1",
    "8.8.8.8"
  ],
  "gateway": "192.168.2.1",
  "routes": {},
  "owner_uuids": [
    "4b60d82b-d858-4c5a-ea3e-a0b9b411d65d"
  ],
  "netmask": "255.255.255.0"
}

account:

/g/i/consul ❯❯❯ tt account get                                                                                                                                         ✘ 1 
id: 4b60d82b-d858-4c5a-ea3e-a0b9b411d65d
login: smith
email: [email protected]
companyName: rawr
firstName: bruce
lastName: smith
triton_cns_enabled: true
phone: 510-123-3214
updated: 2018-11-30T18:38:14.208Z (5d)
created: 2018-11-07T04:33:26.359Z (4w)

RBAC: Cannot set `role-tag` on `/my/config`

I'm not able to use HTTP PUT to update the role-tag on /my/config.

Reproduction setup, the usual environment variables and also:

function cloudapi() {
    local now=$(date -u '+%a, %d %h %Y %H:%M:%S GMT')
    local signature=$(echo -n "$now" | openssl dgst -sha256 -sign ~/.ssh/id_rsa | openssl enc -e -a | tr -d '\n')
    local url="$SDC_URL$1"
    shift
    curl --silent --insecure \
        --header 'Accept: application/json' \
        --header "accept-version: ~8" \
        --header "Date: $now" \
        --header "Authorization: Signature keyId=\"/$SDC_ACCOUNT/keys/id_rsa\",algorithm=\"rsa-sha256\" $signature" \
        "$@" "$url"
    echo
}

Make sure keyId matches your account's key name. You may need to create an example role too.

Expected output:

$ cloudapi /my/config --request PUT --header "Content-Type: application/json" --data '{"role-tag": ["network-operator"]}'
{"name":"/$SDC_ACCOUNT/config","role-tag":["network-operator"]}

Actual output:

$ cloudapi /my/config --request PUT --header "Content-Type: application/json" --data '{"role-tag": ["network-operator"]}'
{"code":"InvalidArgument","message":"property \"default_network\": is missing and it is required"}

This server is CloudAPI version 9.3.0, apologies if this is already fixed in a newer version.

keyapi

After my sdc-discuss post, @marsell followed up and said I should file an issue about this, so here it is:

Currently in lib/auth.js there is an implementation of Token-based auth for CloudAPI using keyapi and sdc-securetoken. app.js inserts the function handling this step, tokenAuth, into the chain after signatureAuth, and at present there's no way to escape from signatureAuth that I can see without an error. So in the code as it is I can't find any way to actually use this token auth mechanism to authenticate a request to CloudAPI.

Adding two lines at the top of signatureAuth changes the story however:

if (req.header('X-Auth-Token'))
  return next();

Then I can successfully use sdc-securetoken to generate some JSON to put in X-Auth-Token and authenticate with the current CloudAPI.

Now I'm given to understand that this code is a legacy left over from previous JPC development, which is why it's undocumented and the keyapi repo is still private. However, I'm sure I'm not the only one with a private SDC deployment who is interested in developing their own "customer portal".

At present, the only SDC API that is guaranteed to stay stable over time is CloudAPI, so if developing a customer portal outside of Joyent, sticking to CloudAPI as much as possible seems like the most sensible approach.

However, with the only documented form of authentication against CloudAPI requiring the use of RSA keys, there is a problem: the customer portal needs to act on behalf of users, and in order to do that, the portal needs to either have all of their private keys, or have a reliable way to ask them to sign requests. The first of these is undesirable (I hope for obvious reasons), and the second seems rather difficult with web browsers as they are today.

It would seem like a practical means to allow the customer portal to impersonate users to CloudAPI would be to use some form of shared secret between the two -- used in a form where that secret never has to be on the wire -- so an HMAC or symmetric encrypt-then-MAC scheme would be a good fit.

Keyapi seems to provide exactly such a mechanism and the code is already built into CloudAPI -- however, it is a bit legacy and has some "special" things like handling double-jsoned-json and makes a horrible hash of the normal signed Authorization header on CloudAPI's end. Nobody seems to be making good noises about it as it is, but that doesn't mean it isn't possible to clean it up and make it into something worth having in the API for system integrators developing with SDC.

So basically this issue is a request, to open up the keyapi repository as it is. It may be horrible code nobody seems to like, but it's listed as a dependency in package.json for sdc-cloudapi, included in the pre-built zone images, and really it should either be open, or entirely removed. My preference is strongly for it to be opened, so that anyone who's interested in helping to replace it with a better mechanism for system integrators to use can propose patches for cleaning it up.

ChangeFeed Events seem to come in out of order

It appears that the events aren't being sent in order, or are being sent out of order. The timestamps are a bit puzzling.

I believe the normal state changes would come in this order... shutting_down, down, stopped?

1st Stop

Change (2020-12-16T19:30:53.689Z) =>
    modified: state,zone_state
    state: shutting_down
    internal state: shutting_down
    object: 86586a47
Change (2020-12-16T19:30:55.691Z) =>
    modified: last_modified,state,zone_state,boot_timestamp,pid
    state: stopped
    internal state: stopped
    object: 86586a47
Change (2020-12-16T19:30:55.692Z) =>
    modified: last_modified,state,zone_state,pid
    state: down
    internal state: down
    object: 86586a47

shutting_down, stopped, down

2nd Stop

Change (2020-12-16T19:39:42.033Z) =>
    modified: last_modified,state,zone_state,boot_timestamp,pid
    state: stopped
    internal state: stopped
    object: 86586a47
Change (2020-12-16T19:39:42.037Z) =>
    modified: last_modified,state,zone_state,exit_timestamp,pid
    state: down
    internal state: down
    object: 86586a47
Change (2020-12-16T19:39:42.038Z) =>
    modified: state,zone_state,exit_status,exit_timestamp
    state: shutting_down
    internal state: shutting_down
    object: 86586a47

stopped, down, shutting_down

Could not discover service endpoint for DOCKER_HOST from CloudAPI

Hi, I am testing triton. I have this infrastructure: headnode, node05 and node07. I can log on to the "operations portal" and create new virtual machines. I tried to create VM with the following technologies: KVM, LX and joyent.

However I can not connect via CloudAPI (docker), when i trying to install I get the following error:

[root@devitop ~]# ./sdc-docker-setup.sh -k 192.168.2.4 webmin /root/.ssh/id_rsa
Setting up Docker client for SDC using:
    CloudAPI:        https://192.168.2.4
    Account:         webmin
    Key:             /root/.ssh/id_rsa

If you have a pass phrase on your key, the openssl command will
prompt you for your pass phrase now and again later.

Verifying CloudAPI access.
CloudAPI access verified.

Generating client certificate from SSH private key.
Wrote certificate files to /root/.sdc/docker/webmin

Get Docker host endpoint from cloudapi.
sdc-docker-setup.sh: warn: could not get Docker service endpoint from CloudAPI (no docker service listed)
Could not discover service endpoint for DOCKER_HOST from CloudAPI.

When I execute the command sdc-healthcheck in headnode, it tells me that everything is fine. However when I execute sdc-listdatacenter in cloudapi0 I get the following error:

[root@d841f209-c3f7-4cd3-b79f-8690c6541d22 (MAD-CPD0:cloudapi0) ~/.ssh]# sdc-listdatacenters --account admin --url https://192.168.2.4 --keyId $( ssh-keygen -lf /root/.ssh/sdc.id_rsa | awk 'BEGIN {FS=" "}; {print $2}') -k

/opt/smartdc/cloudapi/node_modules/smartdc/lib/cloudapi.js:3018
            throw (err);
                   ^
Error: true not found in: /root/.ssh
    at _checkPublic (/opt/smartdc/cloudapi/node_modules/smartdc/node_modules/smartdc-auth/lib/index.js:89:23)
    at fs.js:271:14
    at Object.oncomplete (fs.js:107:15)
[root@d841f209-c3f7-4cd3-b79f-8690c6541d22 (MAD-CPD0:cloudapi0) ~/.ssh]#

Services that are running in cloudapi0:

[root@d841f209-c3f7-4cd3-b79f-8690c6541d22 (MAD-CPD0:cloudapi0) ~/.ssh]# svcs -a
STATE          STIME    FMRI
legacy_run     Sep_28   lrc:/etc/rc2_d/S99net_tune
disabled       Sep_28   svc:/system/manifest-import:default
disabled       Sep_28   svc:/system/identity:domain
disabled       Sep_28   svc:/system/early-manifest-import:default
online         Sep_28   svc:/system/svc/restarter:default
online         Sep_28   svc:/network/datalink-management:default
online         Sep_28   svc:/network/ip-interface-management:default
online         Sep_28   svc:/system/filesystem/root:default
online         Sep_28   svc:/network/loopback:default
online         Sep_28   svc:/system/boot-archive:default
online         Sep_28   svc:/system/filesystem/usr:default
online         Sep_28   svc:/system/device/local:default
online         Sep_28   svc:/system/filesystem/smartdc:default
online         Sep_28   svc:/milestone/devices:default
online         Sep_28   svc:/system/filesystem/minimal:default
online         Sep_28   svc:/system/name-service-cache:default
online         Sep_28   svc:/smartdc/mdata:fetch
online         Sep_28   svc:/network/physical:default
online         Sep_28   svc:/network/routing-setup:default
online         Sep_28   svc:/milestone/network:default
online         Sep_28   svc:/network/initial:default
online         Sep_28   svc:/network/service:default
online         Sep_28   svc:/network/dns/client:default
online         Sep_28   svc:/milestone/name-services:default
online         Sep_28   svc:/system/identity:node
online         Sep_28   svc:/milestone/single-user:default
online         Sep_28   svc:/milestone/sysconfig:default
online         Sep_28   svc:/network/netmask:default
online         Sep_28   svc:/system/filesystem/local:default
online         Sep_28   svc:/smartdc/agent/amon-agent:default
online         Sep_28   svc:/system/system-log:default
online         Sep_28   svc:/system/cron:default
online         Sep_28   svc:/milestone/multi-user:default
online         Sep_28   svc:/milestone/multi-user-server:default
online         Sep_28   svc:/smartdc/mdata:execute
online         Sep_28   svc:/smartdc/application/cloudapi:cloudapi-8084
online         Sep_28   svc:/smartdc/application/cloudapi:cloudapi-8081
online         Sep_28   svc:/smartdc/application/cloudapi:cloudapi-8083
online         Sep_28   svc:/smartdc/application/cloudapi:cloudapi-8082
online         Sep_28   svc:/cloudapi/haproxy:default
online         Sep_28   svc:/pkgsrc/stud:default
online         Sep_28   svc:/smartdc/application/config-agent:default
online         Sep_28   svc:/manta/application/registrar:default

Does this problem is due to a bug? or have I to configure something for CloudAPI?

RBAC: Cannot set `role-tag` on `/my/fabrics` or descendants

I'm not able to use HTTP PUT to update the role-tag on /my/fabrics or any of its descendants.

Reproduction setup, the usual environment variables and also:

function cloudapi() {
    local now=$(date -u '+%a, %d %h %Y %H:%M:%S GMT')
    local signature=$(echo -n "$now" | openssl dgst -sha256 -sign ~/.ssh/id_rsa | openssl enc -e -a | tr -d '\n')
    local url="$SDC_URL$1"
    shift
    curl --silent --insecure \
        --header 'Accept: application/json' \
        --header "accept-version: ~8" \
        --header "Date: $now" \
        --header "Authorization: Signature keyId=\"/$SDC_ACCOUNT/keys/id_rsa\",algorithm=\"rsa-sha256\" $signature" \
        "$@" "$url"
    echo
}

Make sure keyId matches your account's key name. You may need to create an example role too.

Expected output:

$ cloudapi /my/fabrics --request PUT --header "Content-Type: application/json" --data '{"role-tag": ["network-operator"]}'
{"name":"/$SDC_ACCOUNT/fabrics","role-tag":["network-operator"]}

Actual output:

$ cloudapi /my/fabrics --request PUT --header "Content-Type: application/json" --data '{"role-tag": ["network-operator"]}'
{"code":"ResourceNotFound","message":"fabrics is not a valid resource"}

$ cloudapi /my/fabrics/default --request PUT --header "Content-Type: application/json" --data '{"role-tag": ["network-operator"]}'
{"code":"ResourceNotFound","message":"fabrics is not a valid resource"}

$ cloudapi /my/fabrics/default/vlans --request PUT --header "Content-Type: application/json" --data '{"role-tag": ["network-operator"]}'
{"code":"MethodNotAllowedError","message":"PUT is not allowed"}

$ cloudapi /my/fabrics/default/vlans/2 --request PUT --header "Content-Type: application/json" --data '{"role-tag": ["network-operator"]}'
{"code":"InvalidArgument","message":"property \"role-tag\": unsupported property"}

This server is CloudAPI version 9.3.0, apologies if this is already fixed in a newer version. This may or may not be related to #30 .

Provision IP outside of Range

Was able to deploy a reserved address outside the provisionable range of the network.

Network:

cps_lan                                  37a1cc60-ced2-4e59-a62f-1d40525dc8b8  2027   10.91.206.0/24    10.91.206.254
    UUID 37a1cc60-ced2-4e59-a62f-1d40525dc8b8
    Owners
    7e600efd-696c-699a-b21c-e83e590e638f
    aa2ebc36-803f-4c57-9f69-f09419d3a43a
    b083728b-8d87-42bf-f633-be04f3038460
    VLAN ID 2027
    Network 10.91.206.0/24
    Gateway 10.91.206.254
    Netmask 255.255.255.0
    IP Range 10.91.206.11 - 10.91.206.20
    NIC Tag external
    Resolvers 10.45.137.14,10.45.137.15
    MTU 1500
    Description CPS IP Admins

Instance Provisioned with

triton  inst create cfa9c88e-03f8-11eb-9980-879ff7980a9f con-2c-4m-50d-gpc --nic ipv4_uuid=37a1cc60-ced2-4e59-a62f-1d40525dc8b8,ipv4_ips=10.91.206.1 -n ip-test

token based authentication issues with account being interpreted as string vs object

I seem to be at a stopping point. I've developed a simple test suite which is posted below (so no one else has to go through the deciphering again!).

function cloudapi() {
	# POST to the keyapi token auth server..
	authtoken=`curl -X POST -d '{"account": "admin", "devkeyId": “/callinguser/keys/id_rsa”, "permissions": { "cloudapi": ["/my/keys"] }, "expires": "2019-07-10T09:23:45.398Z"}' http://10.12.15.209/token`

	local now=$(date -u '+%a, %d %h %Y %H:%M:%S GMT');
	local signature=$(echo -n "$now" | openssl dgst -sha256 -sign ~/.ssh/id_rsa | openssl enc -e -a | tr -d '\n');
	local url="https://hostname/$1";
	shift;
	curl -s -k -i -H 'Accept: application/json' -H "accept-version: ~8" \
		-H "Date: $now" \
		-H "Authorization: Signature keyId=\”/callinguser/keys/id_rsa\”,algorithm=\"rsa-sha256\" $signature" \
		-H "X-auth-token: ${authtoken}" \
		 "$url"
}

cloudapi $1

and using a simple keyapi server for /token (forgive me for not knowing node!):

var http = require('http');
var url = require('url');
var Keyapi = require('keyapi')
var UFDS = require('ufds');
var Bunyan = require('bunyan');
var restify = require('restify');

var bunyan = new Bunyan({ name: 'keyapi' });

var ufdsOptions = {
    url: 'ldaps://10.12.150.20',
    bindDN: 'cn=root',
    bindPassword: 'secret',
    log: bunyan
};

var ufds;
var keyapi;


// Create http server.
var httpServer = http.createServer(function (req, resp) {
    ufds = new UFDS(ufdsOptions);
    var keyapi = new Keyapi({ log: bunyan, ufds: ufdsOptions });
    var reqUrlString = req.url;
    var pathName = url.parse(reqUrlString, true, false).pathname;
    if('/token' == pathName)
    {
        var method = req.method;
        if("POST" == method)
        {
            var postData = '';
            req.on('data', function (chunk) {
                postData += chunk;
            });
            req.on('end', function () {
                var postDataObject = JSON.parse(postData);
                keyapi.token(postDataObject, function (err, token) {
                        resp.end(JSON.stringify(token));
                })

            })
        }
    }
});

httpServer.listen(80);

After running my test, I receive an error of InvalidCredentials:

$ ./test-auth_as_user.sh my/keys

HTTP/1.1 401 Unauthorized
Content-Type: application/json
Content-Length: 94
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, Api-Version, Response-Time
Access-Control-Allow-Methods: POST, GET, HEAD
Access-Control-Expose-Headers: Api-Version, Request-Id, Response-Time
Connection: Keep-Alive
Content-MD5: ZHr/q0kH6ganClU5iLN5zA==
Date: Wed, 10 Jul 2019 00:33:23 GMT
Server: cloudapi/9.5.1
Api-Version: 9.0.0
Request-Id: 355825d4-2e86-43c7-b647-d5a26f3e7226
Response-Time: 77

{"code":"InvalidCredentials","message":"You must make authenticated requests to use CloudAPI"}

and the corresponding logs on the cloudapi0 server:

{"name":"cloudapi","hostname":"b7a3c410-51a2-4967-8ca9-11ae19f05eb9","pid":61466,"component":"audit","audit":true,"level":30,"remoteAddress":"10.12.14.1","remotePort":39067,"req_id":"355825d4-2e86-43c7-b647-d5a26f3e7226","req":{"method":"GET","url":"/my/keys","headers":{"host":"hostname","user-agent":"curl/7.54.0","accept":"application/json","accept-version":"~8","date":"Wed, 10 Jul 2019 00:33:16 GMT","authorization":"Signature keyId=\"/callinguser/keys/id_rsa\",algorithm=\"rsa-sha256\" q70pn3DHOPFbHYrF3t8lKwmEEpu3+fDP3w0er6qXdWkc7UcScZMQVa6RjdwW+lQXXJCfN2cP4A2Do/lRXUu8GiKWQ+7hejL12Z63opb68cx1onWxUHv7+MCIfeRGnIBf//OLPkQYc08CuAQ/hNweWQwKZp/VF6cKmo53mXQ0QkZ0I4LR/KrMSm9PTmQ/ricPhzhKHDJ558244bqMU4H99xa3v48Hcg4t5kmp2WPi3cRT7Qna7ThO7rodleDgWyuLYtWFXtkllc3pVMQDugHXS3jXj6vxpoQfDIDLpv0tYlsFdfSY1h5ZO6t4ogeqZPHlrsyBLiWxHlipVAqMbZ/0JQDzJlb8V/hdkFBUdJn6dmukmeWhHtw+mqEvr+dD1O+psEFDPdNBptzNfsxUjE3PBEDM2zHgZfIHVi2/h63eG4ys/gIU10nsVrrq4tmzmmMVHgNIFs4uTbYII4Bv4679spX4hJnoXDsLYDNW16FizxvVjoEJTH9up2qF9Kwm7jb2rkM7enIaetUPZURJ7zlyfBUhT/Tw03u/4JZwa+xu6yNv0BuuXnd/QUU5htv0/EbbOX5IzZk3Bc+FoDd7cUjoRb/LjoICLwXvsJwdto+XLgN8opDCfWABNq0F4u/3BqCquOeLMHuFh0JOwgY/6O74AYnt7QakmYn/dgnYpV6n9dI=","x-auth-token":"{\"keyid\":\"68a27d5f-f022-4722-ad2b-6b005cea7bd5\",\"data\":\"3T9SLdivAFTRmxa54OxX+Fr4CPfq91e/ih312WhK1Qq5RqdwgrxYzJplGT63Ued4Z+Edi8fpvATY7lUdvRE1XlSfTM6JfnME7p9Hz/AvllUK+cn0g3t7h7x03HnzRMGLf9uzgz+tcsOwSmVeeBT7F0apIEaMsMG4Pl7OPhXAsseeUQ3OqRsxdRphZg1SZ7X38qvV1kyZwe5f702/qn+96AC4y4U4cHat+n28vFSnlBprfujVICZgbQOJRIf9lwbx5gx1k5hniwlaYZbfelheVzbOy5mSgdNLs+bUkdDAxnD+bCEkjz908ZXlJNpZOsvPz1Ud597SDR2XCsafQWI/ZRiEqtqIT8jM4z5eky+op9imSGZZy+l/3Ac9NwaQPnivuggdtR0h4ESSDVaZFX8WpYbPuV/7gL/YN5ms5rkTp2U=\",\"version\":\"0.1.0\",\"hash\":\"IOY0sbWKEB3a9D2iTM6nVy+i0IXtO1NquxC/OCgFyvU=\"}","x-forwarded-for":"::ffff:10.12.14.1"},"httpVersion":"1.1","trailers":{},"timers":{"parseAccept":155,"parseAuthorization":2786,"parseDate":41,"bunyan":110,"parseQueryString":43,"readBody":75,"parseBody":14,"restifyResponseHeaders":10,"xForwardedFor":146,"setupSDCProxies":48,"accountMgmt":1504,"signatureAuth":63241,"tokenAuth":5023,"assertAuthenticated":1907}},"res":{"statusCode":401,"headers":{"content-type":"application/json","content-length":94,"access-control-allow-origin":"*","access-control-allow-headers":"Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, Api-Version, Response-Time","access-control-allow-methods":"POST, GET, HEAD","access-control-expose-headers":"Api-Version, Request-Id, Response-Time","connection":"Keep-Alive","content-md5":"ZHr/q0kH6ganClU5iLN5zA==","date":"Wed, 10 Jul 2019 00:33:23 GMT","server":"cloudapi/9.5.1","api-version":"9.0.0","request-id":"355825d4-2e86-43c7-b647-d5a26f3e7226","response-time":77},"trailer":false},"err":{"message":"You must make authenticated requests to use CloudAPI","name":"InvalidCredentialsError","stack":"InvalidCredentialsError: You must make authenticated requests to use CloudAPI\n    at Server.assertAuthenticated (/opt/smartdc/cloudapi/lib/auth.js:405:21)\n    at next (/opt/smartdc/cloudapi/node_modules/restify/lib/server.js:912:30)\n    at f (/opt/smartdc/cloudapi/node_modules/restify/node_modules/once/once.js:25:25)\n    at setCreds (/opt/smartdc/cloudapi/lib/auth.js:372:20)\n    at deJson (/opt/smartdc/cloudapi/node_modules/keyapi/index.js:132:12)\n    at /opt/smartdc/cloudapi/node_modules/keyapi/index.js:64:20\n    at /opt/smartdc/cloudapi/node_modules/keyapi/node_modules/sdc-securetoken/lib/securetoken.js:165:16\n    at Gunzip.onEnd (zlib.js:227:5)\n    at emitNone (events.js:72:20)\n    at Gunzip.emit (events.js:166:7)"},"latency":77,"route":"listkeys","_audit":true,"msg":"handled: 401","time":"2019-07-10T00:33:23.272Z","v":0}

So, what I’ve deciphered (and this is very likely wrong with just digging into this!).. The account being based in the x-auth-token is being sent over, and interpreted as a string, rather than object at line 404.

Is my non tokenized authtoken using the proper format? Or is token auth broken?

See the line below where the object vs. string seems to be giving difficulties:

https://github.com/joyent/sdc-cloudapi/blob/48167a834eac55218c51facf76141001fcc0765a/lib/auth.js#L404

AddNic should accept a "primary" argument

Currently when creating a new nic with AddNic via cloudapi, it's not possible to make the new NIC the "primary" NIC for the VM. This is quite frustrating, since there is no other mechanism to make a NIC primary after creation through cloudapi either. Our users currently end up having to file tickets to get us to go and correct the situation by hand.

users.test.js failing due to stale policy checks

users.test.js used to create one policies for testing in tests/common.js, and one policy explicitly within users.test.js. At some point an additional policy was added to common.js, but checks in users.test.js were not updated.

plugins tests not updated to use tape

The tests in tests/plugins still refer to @smaller/tap, but cloudapi now uses tape. Running the tests in tests/plugins fails.

Sadly, no longer have logs for this to post here.

Instance resize fails with differing traits error

arch@9d46dd2d-1178-ce83-cbc4-d396e4a24060 /g/sdc-cloudapi ❯❯❯ tt inst resize thatch c5f53a16-6e7b-4827-bf1f-9bf1244f5df3                                           changefeed ✭ ✱
Resizing instance thatch to "c5f53a16-6e7b-4827-bf1f-9bf1244f5df3"
triton instance resize: error (InvalidArgument): Resizing to package with differing traits not supported

Seem's like CloudAPI tries to do the following (https://github.com/joyent/sdc-cloudapi/blob/master/lib/machines.js#L2150)

 if (!jsprim.deepEqual(req.pkg.traits, req.machine.package.traits)) {

But req.machine.package.traits is undefined.

I believe this is because in tranlsate() we are returning packages[0].name instead of the package object. (https://github.com/joyent/sdc-cloudapi/blob/master/lib/machines.js#L241)

        msg.package = packages[0] ? packages[0].name : '';

When I changed it to the following it worked.

        msg.package = packages[0] ? packages[0] : '';

edit :

I had to warp that in a conditional so that it doesn't break Client Libraries. The Trition Client Libraries may depend on package being a string and not a object.

        if (req.params.action === 'resize') {
            msg.package = packages[0] ? packages[0] : '';
        }

nics.test.js failing due to double callback

Uncaught AssertionError: idx should be equal to ndone

FROM
fail (assert.js:84:3)
Function.equal (assert.js:104:27)
next (/opt/smartdc/cloudapi/node_modules/vasync/lib/vasync.js:805:14)
/opt/smartdc/cloudapi/test/nics.test.js:617:21
/opt/smartdc/cloudapi/test/common.js:1113:13
/opt/smartdc/cloudapi/node_modules/sdc-clientsok 11 deleteNicTag sdccloudapitest_nics_nictag1
/lib/restifyclient.js:121:20
parseResponse (/opt/smartdc/cloudapi/node_modules/sdc-clients/node_modules/restify-clients/lib/JsonClient.js:105:9)
IncomingMessage.done (/opt/smartdc/cloudapi/node_modules/sdc-clients/node_modules/restify-clients/lib/StringClient.js:237:13)
IncomingMessage.g (events.js:292:16)
emitNone (events.js:91:20)
IncomingMessage.emit (events.js:185:7)
endReadableNT (_stream_readable.js:978:12)
_combinedTickCallback (internal/process/next_tick.js:80:11)
process._tickCallback (internal/process/next_tick.js:104:9)
./runtests: line 114: 79569 Illegal Instruction     (core dumped) PATH=${NODE_INSTALL}/bin:${PATH} ${NODE_INSTALL}/bin/node --abort_on_uncaught_exception ${t}
     79570 Done                    | tee -a ${OUTPUT_DIR}/cloudapi.tap

CloudAPI should include ability to reprovision a machine

Currently CloudAPI is missing ability to reprovision a machine to a current or newer image version.
Therefore also Triton CLI is missing the ability.

-Is there a technical reasoning behind this or is this just not implemented yet?

Reprovisioning is a useful feature when doing rolling upgrades and not wanting to spin up new instance for the upgrade (a little bit of container anti-pattern but useful in limited use-cases).

This would be "nice to have" feature in the long run but not a must - more modern way would be to just spin up a new instance and fail over to that when ready.

cueball HTTP agent config missing pinger or TCP keepalives

The current CueballHttpAgent config we're using in cloudapi is missing a pinger or TCP keepalive settings, which means that we can easily get "zombie" connections or races when cloudapi is talking to other components (like imgapi or napi) -- while cloudapi is writing a request out to a socket, the other end is closing it, and we get ECONNRESET errors.

It seems like all of the downstream services have a /ping endpoint, or at least don't respond with an error to that URI, so we should just need to add:

        "tcpKeepAliveInitialDelay": 10000,
        "ping": "/ping",
        "pingInterval": 60000,

We'll have to update the version of cueball as well, since the version we're using at the moment (2.1.1) has a bug that causes pingers to misbehave (TritonDataCenter/node-cueball#102)

Tests failing due to changed tape behaviour

package.json uses tape 5.0.0 and above, but tape switched deep-equal library versions between 4.x and 5.x, and the new deep-equal has some strange behaviour: deepEqual() failing with objects that are semantically identical, but have a different ordering of attributes. For example:

    operator: deepEqual
    expected: |-
      { jse_info: {}, jse_shortmsg: '', message: 'Role(s) asdasdasdasd not found', statusCode: 409, restCode: 'InvalidArgument', name: 'InvalidArgumentError', body: { code: 'InvalidArgument', message: 'Role(s) asdasdasdasd not found' } }
    actual: |-
      { jse_shortmsg: '', jse_info: {}, message: 'Role(s) asdasdasdasd not found', statusCode: 409, body: { code: 'InvalidArgument', message: 'Role(s) asdasdasdasd not found' }, restCode: 'InvalidArgument', name: 'InvalidArgumentError' }
    at: <anonymous> (/opt/smartdc/cloudapi/test/auth.test.js:467:11)
    stack: |-
      InvalidArgumentError: Role(s) asdasdasdasd not found
          at parseResponse (/opt/smartdc/cloudapi/node_modules/restify/lib/clients/json_client.js:71:23)
          at IncomingMessage.done (/opt/smartdc/cloudapi/node_modules/restify/lib/clients/string_client.js:167:13)
          at IncomingMessage.g (events.js:292:16)
          at emitNone (events.js:91:20)
          at IncomingMessage.emit (events.js:185:7)
          at endReadableNT (_stream_readable.js:978:12)
          at _combinedTickCallback (internal/process/next_tick.js:80:11)
          at process._tickCallback (internal/process/next_tick.js:104:9)

I could fix the tests themselves, but since this new deepEqual() behaviour is nonsense, switch to using the older tape 4.13.2 instead.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.