Technical Articles
F5 SMEs share good practice.
cancel
Showing results for 
Search instead for 
Did you mean: 
JRahm
Community Manager
Community Manager

 iControl REST. It’s iControl SOAP’s baby, brother, introduced back in TMOS version 11.4 as an early access feature but released fully in version 11.5.

Several articles on basic usage have been written on iControl REST so the intent here isn’t basic use, but rather to demystify some of the finer details of using the API. This article will cover the details on how to transfer files to/from the BIG-IP using iControl REST and the python programming language. (Note: this functionality requires 12.0+.)

The REST File Transfer Worker

The file transfer worker allows a client to transfer files through a series of GET operations for downloads and POST operations for uploads. The Content-Range header is used for both as a means to chunk the content. For downloads, the worker listens on the following interfaces.

Description Method URI File Location
Download a File GET /mgmt/cm/autodeploy/software-image-downloads/ /shared/images/
Upload an Image File POST /mgmt/cm/autodeploy/software-image-uploads/ /shared/images/
Upload a File POST /mgmt/shared/file-transfer/uploads/ /var/config/rest/downloads/
Download a QKView GET /mgmt/shared/file-transfer/qkview-downloads/ /var/tmp/
Download a UCS GET /mgmt/shared/file-transfer/ucs-downloads/ /var/local/ucs/
Upload ASM Policy POST /mgmt/tm/asm/file-transfer/uploads/  
Download ASM Policy GET /mgmt/tm/asm/file-transfer/downloads/  

Binary and text files are supported. The magic in the transfer is the Content-Range header, which has the following format:

Content-Range: start-end/filesize

Where start/end are the chunk's delimiters in the file and filesize is well, the file size. Any file larger than 1M needs to be chunked with this header as that limit is enforced by the worker. This is done to avoid potential denial of service attacks and out of memory errors. There are benefits of chunking as well: 

  • Accurate progress bars
  • Resuming interrupted downloads
  • Random access to file content possible 

Uploading a File

The function is shown below. Note that whereas normally with the REST API the Content-Type is application/json, with file transfers that changes to application/octet-stream. The workflow for the function works like this (line number in parentheses) :

  1. Set the Chunk Size (3)
  2. Set the Content-Type header (4-6)
  3. Open the file (7)
  4. Get the filename (apart from the path) from the absolute path (8)
  5. If the extension is an .iso file (image) put it in /shared/images, otherwise it’ll go in /var/config/rest/downloads (9-12)
  6. Disable ssl warnings requests (required with my version: 2.8.1. YMMV) (14)
  7. Set the total file size for use with the Content-Range header (15)
  8. Set the start variable to 0 (17)
  9. Begin loop to iterate through the file and upload in chunks (19)
  10. Read data from the file and if there is no more data, break the loop (20-22)
  11. set the current bytes read, if less than the chunk size, then this is the last chunk, so set the end to the size from step 7. Otherwise, add current bytes length to the start value and set that as the end. (24-28)
  12. Set the Content-Range header value and then add that to the header (30-31)
  13. Make the POST request, uploading the content chunk (32-36)
  14. Increment the start value by the current bytes content length (38)
def _upload(host, creds, fp):

    chunk_size = 512 * 1024
    headers = {
        'Content-Type': 'application/octet-stream'
    }
    fileobj = open(fp, 'rb')
    filename = os.path.basename(fp)
    if os.path.splitext(filename)[-1] == '.iso':
        uri = 'https://%s/mgmt/cm/autodeploy/software-image-uploads/%s' % (host, filename)
    else:
        uri = 'https://%s/mgmt/shared/file-transfer/uploads/%s' % (host, filename)

    requests.packages.urllib3.disable_warnings()
    size = os.path.getsize(fp)

    start = 0

    while True:
        file_slice = fileobj.read(chunk_size)
        if not file_slice:
            break

        current_bytes = len(file_slice)
        if current_bytes < chunk_size:
            end = size
        else:
            end = start + current_bytes

        content_range = "%s-%s/%s" % (start, end - 1, size)
        headers['Content-Range'] = content_range
        requests.post(uri,
                      auth=creds,
                      data=file_slice,
                      headers=headers,
                      verify=False)

        start += current_bytes

Downloading a File

Downloading is very similar but there are some differences. Here is the workflow that is different, followed by the code. Note that the local path where the file will be downloaded to is given as part of the filename.

  1. URI is set to downloads worker. The only supported download directory at this time is /shared/images. (8)
  2. Open the local file so received data can be written to it (11)
  3. Make the request (22-26)
  4. If response code is 200 and if size is greater than 0, increment the current bytes and write the data to file, otherwise exit the loop (28-40)
  5. Set the value of the returned Content-Range header to crange and if initial size (0), set the file size to the size variable (42-46)
  6. If the file is smaller than the chunk size, adjust the chunk size down to the total file size and continue (51-55)
  7. Do the math to get ready to download the next chunk (57-62)
def _download(host, creds, fp):
    chunk_size = 512 * 1024

    headers = {
        'Content-Type': 'application/octet-stream'
    }
    filename = os.path.basename(fp)
    uri = 'https://%s/mgmt/cm/autodeploy/software-image-downloads/%s' % (host, filename)
    requests.packages.urllib3.disable_warnings()

    with open(fp, 'wb') as f:
        start = 0
        end = chunk_size - 1
        size = 0
        current_bytes = 0

        while True:
            content_range = "%s-%s/%s" % (start, end, size)
            headers['Content-Range'] = content_range

            #print headers
            resp = requests.get(uri,
                                auth=creds,
                                headers=headers,
                                verify=False,
                                stream=True)

            if resp.status_code == 200:
                # If the size is zero, then this is the first time through the
                # loop and we don't want to write data because we haven't yet
                # figured out the total size of the file.
                if size > 0:
                    current_bytes += chunk_size
                    for chunk in resp.iter_content(chunk_size):
                        f.write(chunk)

                # Once we've downloaded the entire file, we can break out of
                # the loop
                if end == size:
                    break

            crange = resp.headers['Content-Range']

            # Determine the total number of bytes to read
            if size == 0:
                size = int(crange.split('/')[-1]) - 1

                # If the file is smaller than the chunk size, BIG-IP will
                # return an HTTP 400. So adjust the chunk_size down to the
                # total file size...
                if chunk_size > size:
                    end = size

                # ...and pass on the rest of the code
                continue

            start += chunk_size

            if (current_bytes + chunk_size) > size:
                end = size
            else:
                end = start + chunk_size - 1

Now you know how to upload and download files. Let’s do something with it!

A Use Case - Upload Cert & Key to BIG-IP and Create a Clientssl Profile!

This whole effort was sparked by a use case in Q&A, so I had to deliver the goods with more than just moving files around. The complete script is linked at the bottom, but there are a few steps required to get to a clientssl certificate:

  1. Upload the key & certificate
  2. Create the file object for key/cert
  3. Create the clientssl profile

You know how to do step 1 now. Step 2 is to create the file object for the key and certificate. After a quick test to see which file is the certificate, you set both files, build the payload, then make the POST requests to bind the uploaded files to the file object.

def create_cert_obj(bigip, b_url, files):

    f1 = os.path.basename(files[0])
    f2 = os.path.basename(files[1])
    if f1.endswith('.crt'):
        certfilename = f1
        keyfilename = f2
    else:
        keyfilename = f1
        certfilename = f2

    certname = f1.split('.')[0]

    payload = {}
    payload['command'] = 'install'
    payload['name'] = certname

    # Map Cert to File Object
    payload['from-local-file'] = '/var/config/rest/downloads/%s' % certfilename
    bigip.post('%s/sys/crypto/cert' % b_url, json.dumps(payload))

    # Map Key to File Object
    payload['from-local-file'] = '/var/config/rest/downloads/%s' % keyfilename
    bigip.post('%s/sys/crypto/key' % b_url, json.dumps(payload))

    return certfilename, keyfilename

Notice we return the key/cert filenames so they can be used for step 3 to establish the clientssl profile. In this example, I name the file object and the clientssl profile to the name of the certfilename (minus the extension) but you can alter this to allow the objects names to be provided. To build the profile, just create the payload with the custom key/cert and make the POST request and you are done!

def create_ssl_profile(bigip, b_url, certname, keyname):
    payload = {}
    payload['name'] = certname.split('.')[0]
    payload['cert'] = certname
    payload['key'] = keyname
    bigip.post('%s/ltm/profile/client-ssl' % b_url, json.dumps(payload))

Much thanks to Tim Rupp who helped me get across the finish line with some counting and rest worker errors we were troubleshooting on the download function.

Get the Code

 

Comments
Zdenda
Cirrus
Cirrus
Hi, thx for very nice article, but I am not able to run it. Whatever I try I always get 400 as a response and no file, or part of file is copied to LB. I am not able to find what can be wrong. I tried both scripts: "Upload a File" and "Upload Cert/Key & Build a Clientssl Profile". Do you have any clue what I might have wrong? Thanks, Zdenek.
Zdenda
Cirrus
Cirrus
I use version 11.5.2 and don't have problem with common API REST calls
Arnaud_Lemaire
F5 Employee
F5 Employee
as an addition asm file upload is available as well (not sure about the version minimum) https://%s/mgmt/tm/asm/file-transfer/uploads/%s file upload will be done in /ts/var/rest/ and then policy can be imported with : POST /mgmt/tm/asm/tasks/import-policy { "filename":"" "name":"" }
rodolfosalgado_
Altostratus
Altostratus

Thanks, this article helped to convert your python code to powershell. I wouldn't be able to do it without your example.

 

clammers
Nimbostratus
Nimbostratus

Has someone tested this with token-based authentication? (User is mapped to Administrator)

 

Line 20 in create_cert_obj throwing:

 

{"code":400,"message":"Unable to copy (/var/config/rest/downloads/test-vip.crt) into tempfile (/var/system/tmp/tmsh/yrfD5H/test-vip.crt), Permission denied","errorStack":[],"apiError":26214401}

 

test-vip.crt has root:root 600 permission after upload. Changing file-mode is no option, cause it throws:

 

{"code":400,"message":"Key management library returned bad status: -4, Invalid Parameter","errorStack":[],"apiError":26214401}

 

Using local auth with root works.

 

JRahm
Community Manager
Community Manager

I'll retest with token auth and let you know. I'll also be updating methods from /sys/crypto to /sys/file/ssl-key and /sys/file/ssl-cert as well. It'll be Monday before I have a chance. I'm on PTO today.

 

clammers
Nimbostratus
Nimbostratus

Hello Jason,

 

any updates on this? I would really appreciate it - still having no solution for this issue.

 

JRahm
Community Manager
Community Manager

Yes, sorry for the delay. With the latest version of the sdk (2.0) I can successfully upload a file that I verified in the BIG-IP's /var/config/rest/downloads directory with remote authentication using tokens (i am testing on 12.1.) user_admin/letmein00 are only defined in my tacacs test server.

 

from f5.bigip import ManagmementRoot mr = ManagementRoot('ltm3.test.local', 'user_admin', 'letmein00', token=True) mr.shared.file_transfer.uploads.upload_file('/var/tmp/tempfile')
clammers
Nimbostratus
Nimbostratus

Hello Jason,

 

thanks for quick response. Uploading is no problem - but when accessing the uploaded file with the same credentials to install it to the f5 key-cert-store it fails (see code 400 error in my previous post). Can you please have a look on this?

 

JRahm
Community Manager
Community Manager

Hi Christian, sorry for missing the point.

 

Edit - I'm investigating this issue.

 

clammers
Nimbostratus
Nimbostratus

Hey Jason,

 

do you have any updates on this issue? It would be really great 🙂

 

JRahm
Community Manager
Community Manager

what version/hotfix are you on?

 

clammers
Nimbostratus
Nimbostratus

i am using 12.1.0 build 0.0.1434

 

JRahm
Community Manager
Community Manager

ok I'll test with that, but will likely be in the morning. I think I saw the same behavior with tokens when I tested last. You might open a case as well.

 

Feel free to reach out off-site at j -dot- rahm -at- f5 -dot- com and I can work with you directly on this.

 

JRahm
Community Manager
Community Manager

Hi Christian, it doesn't seem to be a problem with tokens, as I can use tokens with the built-in accounts just fine, but if I'm using remote-auth and tokens, that fails to permissions issues. Did you get a case open on this?

 

__J___262279
Nimbostratus
Nimbostratus

In case anyone else struggled with this in powershell (I sure did for a bit). Here is an simple function that will do the trick.

 

function F5-RestFileUpload { < .SYNOPSIS Upload a file to the BigIP .DESCRIPTION Uploads file to the F5 in to /var/config/rest/downloads directory. .EXAMPLE F5-RestFileUpload -InFile "C:\certs\mycert.pxf" -BigIP "mybigip.local" -Credential $mycreds > [CmdletBinding()] PARAM ( [string][parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$InFile, [string][parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$BigIP, [System.Management.Automation.PSCredential]$Credential ) BEGIN { $file = Get-ChildItem $InFile $byte = Get-Content $file.FullName -Encoding Byte $ContentType = "application/octet-stream" $ContentRange = "0-{0}/{1}" -f ($file.length -1), $file.length $path = "/mgmt/shared/file-transfer/uploads" $uri = "https://{0}{1}/{2}" -f $BigIP, $path, $file.name $uri = New-Object System.Uri $uri } PROCESS { $webclient = New-Object System.Net.WebClient $webclient.Headers.Add("ServerHost", $uri.DnsSafeName); $webclient.Headers.Add("Content-Type", $ContentType) $webclient.Headers.Add("Content-Range", $ContentRange) $webclient.Credentials = $Credential try { $response = $webclient.uploaddata($uri.AbsoluteUri, $byte) } catch [Exception] { $PSCmdlet.ThrowTerminatingError($_) } finally { if($null -ne $webclient) { $webclient.Dispose() } } } END { } }
clammers
Nimbostratus
Nimbostratus

hello jason,

 

sorry for late response but i got it working in 12.1.2 with uploading and also installing certificates with radius auth. Thanks for your support!

 

JRahm
Community Manager
Community Manager

hi @Christian, thanks for the update. I'll test 12.1.2 as well to see if that improves on my end.

 

Chris_Hiner_263
Nimbostratus
Nimbostratus

This was very helpful. I ended up having my script build a pkcs12 file and import that, since otherwise I couldn't upload a new key/cert pair if the key changed without getting "profile X's key and certificate do not match".

 

After the file upload then POST to /mgmt/tm/sys/crypto/pkcs12 with a payload of:

 

command=install name=name_to_show_in_ssllist from-local-file=/var/config/rest/downloads/nameof.file.pkcs12 passphrase=password_for_pkcs12
mchaas
Nimbostratus
Nimbostratus

Hi all! This post was very helpful indeed. Also the hint with the central authentication and the requirement to upgrade to 12.1.2. However, I probably need another little hint...

 

When I upload a text-file (haven't tried any binary data yet) to the BigIP with the method described above, the file has a leading 0-byte.

 

That's the file on my management-host:

 

[matt@linuxhost rest_bigip]$ hexdump testfile -C 00000000 31 32 33 34 35 36 37 38 39 30 61 62 63 64 65 66 |1234567890abcdef| 00000010 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 |ghijklmnopqrstuv| 00000020 77 78 79 7a 0a |wxyz.| 00000025

after posting it to the BigIP:

 

[matt@linuxhost rest_bigip]$ curl -X POST https://ixi3-lab-lb2-1/mgmt/shared/file-transfer/uploads/testname.txt -H "Content-Type: application/octet-stream" -H "Content-Range: 1-36/36" -H "X-F5-Auth-Token: XXXXXXXXXXXXXX" -d @testfile -vvv * About to connect() to ixi3-lab-lb2-1 port 443 (0) * Trying 10.150.250.156... connected * Connected to ixi3-lab-lb2-1 (10.150.250.156) port 443 (0) * Initializing NSS with certpath: sql:/etc/pki/nssdb * CAfile: xxxx CApath: none * SSL connection using TLS_RSA_WITH_AES_128_CBC_SHA * Server certificate: * xxxxxx > POST /mgmt/shared/file-transfer/uploads/testname.txt HTTP/1.1 > User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2 > Host: ixi3-lab-lb2-1 > Accept: */* > Content-Type: application/octet-stream > Content-Range: 1-36/36 > X-F5-Auth-Token: XXXXXXXXXXXXXX > Content-Length: 36 > < HTTP/1.1 200 OK < Date: 15 Feb 2017 15:21:20 UTC < Server: com.f5.rest.common.RestRequestSender < X-Frame-Options: SAMEORIGIN < Strict-Transport-Security: max-age=16070400; includeSubDomains < Pragma: no-cache < Cache-Control: no-store, no-cache, must-revalidate < Expires: -1 < Content-Length: 241 < Content-Type: application/json < Content-Range: 1-36/36 < Local-Ip-From-Httpd: 10.150.250.156 < X-Forwarded-Server: localhost.localdomain < X-Forwarded-Proto: http < X-Forwarded-Host: ixi3-lab-lb2-1 < X-Content-Type-Options: nosniff < X-XSS-Protection: 1; mode=block < Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' http://127.4.1.1 http://127.4.2.1 < * Connection 0 to host ixi3-lab-lb2-1 left intact * Closing connection 0 {"remainingByteCount":0,"usedChunks":{"1":36},"totalByteCount":36,"localFilePath":"/var/config/rest/downloads/testname.txt","temporaryFilePath":"/var/config/rest/downloads/tmp/testname.txt","generation":0,"lastUpdateMicros":1487172080246573}

The file on the BigIP does not look quite the same (note byte 0 being 0x00, as opposed to 0x31 in the original file):

 

[matt@ixi3-lab-lb2-1:Active:Standalone] ~ xxd /var/config/rest/downloads/testname.txt 0000000: 0031 3233 3435 3637 3839 3061 6263 6465 .1234567890abcde 0000010: 6667 6869 6a6b 6c6d 6e6f 7071 7273 7475 fghijklmnopqrstu 0000020: 7677 7879 7a vwxyz

Also the original "\n" seems to be trimmed off, which isn't a problem in this case. Just mentioning it for the sake of completeness. I tried to use Content-Type: text/plain instead of "application/octet-stream", I tried binary-transfer, nothing seems to help. What am I missing here? Thanks for your help!

 

regards, Matt

 

update:

 

trace-ascii shows that curl seems to be sending the post data correctly

 

=> Send data, 36 bytes (0x24) 0000: 1234567890abcdefghijklmnopqrstuvwxyz <= Recv header, 17 bytes (0x11) 0000: HTTP/1.1 200 OK
mchaas
Nimbostratus
Nimbostratus

Hi again,

 

I just fixed this.

 

-H "Content-Range: 0-35/35"

did the trick.

 

Thanks!

 

Regards, Matt

 

Hi Jason,

thanks for the nice article. Uploading works fine this way even through BIG-IQ used as REST proxy.

I hoped
DELETE
would be supported as well to clean up the temp file from the directory after importing it into the TMOS file store.

As a workaround I tried a
rm
command via
/mgmt/tm/util/bash
which fails with a "401".

Allowing
DELETE
would help me to avoid a
cron
to sanitize the upload directory.

Cheers, Stephan
JRahm
Community Manager
Community Manager

bash works for me to remove the files, are you specifying the -c argument with it?

 

Hi Jason, you are right, thanks a lot!

I had a typo in my call. This one works as expected:

curl -svk -H "Content-Type: application/json" -H "X-F5-Auth-Token: ${token" -X POST -d "{\"command\":\"run\",\"utilCmdArgs\":\"-c 'rm /var/config/rest/downloads/mycert.crt'\"}" "https://${bigiq}/mgmt/shared/resolver/device-groups/cm-bigip-allDevices/devices/${bigipuuid}/rest-proxy/mgmt/tm/util/bash" | json-format

We are programmatically uploading files to our F5 devices.

 

The following command WORKS correctly, but does NOT follow RFC 7233 4.2:

 

curl -i -sk -u admin:admin -X POST -H "Expect:" \ -H "Content-Type: application/octet-stream" \ -H "Content-Range: 0-1333/1334" --data-binary "@domain.crt" \ "https://192.168.1.1/mgmt/shared/file-transfer/uploads/domain.crt"

The following command does NOT work correctly, but DOES follow RFC 7233 4.2: curl -i -sk -u admin:admin -X POST -H "Expect:" \ -H "Content-Type: application/octet-stream" \ -H "Content-Range: bytes 0-1333/1334" --data-binary "@domain.crt" \ ";

 

The main difference is the use of the HTTP Header Content-Range including the word "bytes" with the values. RFC 7233 4.2 indicates that this should be (example from RFC) Content-Range: bytes 42-1233/1234

 

However whenever we include the word "bytes" the F5 responds with a HTTP 400, error follows:

 

HTTP/1.1 400 Bad Request Date: 08 Dec 2017 23:51:54 UTC Server: com.f5.rest.common.RestRequestSender X-Frame-Options: SAMEORIGIN Strict-Transport-Security: max-age=16070400; includeSubDomains Pragma: no-cache Cache-Control: no-store, no-cache, must-revalidate Expires: -1 Content-Length: 135 Content-Type: application/json REMOTEROLE: 0 Local-Ip-From-Httpd: 127.0.0.1 Session-Invalid: true X-Forwarded-Server: localhost.localdomain X-Forwarded-Proto: http REMOTECONSOLE: /sbin/nologin X-Forwarded-Host: 192.168.1.1 X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' http://127.4.1.1 http://127.4.2.1 Connection: close {"code":400,"message":"Missing 'Content-Range' header","referer":"192.168.1.2","restOperationId":66478298,"kind":":resterrorresponse"}%

However whenever we do NOT include the word "bytes" the F5 responds with a HTTP 200 and the file is created on the F5 in the /var/config/rest/downloads directory, but again fails to follow follow RFC 7233 4.2 which our application code is enforcing.

 

JRahm
Community Manager
Community Manager

@Troy: open a case and get a bug submitted for that.

 

@Jason - I opened on last Friday, they've reproduced the issue and escalated it internally.

 

JRahm
Community Manager
Community Manager

Did they give you a bugID?

 

Is it possible to install PKCS 12 cert through Python SDK?

I don't see any interface under bigip.tm.sys.cryto.py like certs and keys. So I tried to use the format where Chris Hiner used above but it didn't work.

    command=install
    name=name_to_show_in_ssllist
    from-local-file=/var/config/rest/downloads/nameof.file.pkcs12
    passphrase=password_for_pkcs12

I tried like this but it didn't work. I wonder what is the correct format for pkcs 12.

param_set = {'from-local-file':'/var/config/rest/downloads/testpkcs.p12', 'name':'testpkcsfile', 'passphrase':'pkcs_pw'}
bigip.tm.sys.crypto.keys.exec_cmd('install',** param_set)
bigip.tm.sys.crypto.certs.exec_cmd('install',** param_set)
JRahm
Community Manager
Community Manager

You need to use these methods:

key = b.tm.sys.file.ssl_keys.ssl_key.create(name=name, sourcePath=sourcepath)
cert = b.tm.sys.file.ssl_certs.ssl_cert.create(name=name, sourcePath=sourcepath)

Where sourcepath is (after you have uploaded the files via iControl REST) file:/var/config/rest/downloads/name.[key|crt]

Mr Rahm,

A little bit confused.

Then, is there any difference using bigip.tm.sys.crypto.keys.exec_cmd() and b.tm.sys.file.ssl_certs.ssl_key.create()? It seems both install a SSL key from the local key file (/var/config/rest/downloads/)

Also, what if I need to specify a passphrase (password) for pkcs12? Simply add another parameter like the following?
 b.tm.sys.file.ssl_keys.ssl_key.create(name=name, sourcePath=sourcepath, passphrase='key_passphrase')

Thank you.

JRahm
Community Manager
Community Manager

Hi @F5_Digger, the sys/crypto endpoint is still there but deprecated, permissions are not guaranteed to work with that, but should with sys/file/ssl_*.

 

Yes, the passphrase parameter should be passed if specified.

 

Ah.. Much clear. Thanks Jason.

 

@Jason

 

I tried it out and got an error

 

For the simple test, I placed a key file under/var/config/rest/downloads/mynew_key.key and then made the following call from Windows IDLE(3.6.2):

 

 

from f5.bigip import ManagementRoot
import requests
import sys
import os

requests.packages.urllib3.disable_warnings()

mr = ManagementRoot('x.x.x.x', 'admin', 'admin')

key = mr.tm.sys.file.ssl_keys.ssl_key.create(name='MyNewkey', partition='Common', sourcePath='/var/config/rest/downloads/mynew_key.key')

Then I got the following error.

 

 

Traceback (most recent call last):
  File "C:\Users\xyz\Documents\Python Practice\ssl cert key file upload.py", line 23, in 
    key = mr.tm.sys.file.ssl_keys.ssl_key.create(name='MyNewkey', partition='Common', sourcePath='/var/config/rest/downloads/mynew_key.key')
  File "C:\Users\xyz\AppData\Local\Programs\Python\Python36-32\lib\site-packages\f5\bigip\resource.py", line 974, in create
    return self._create(**kwargs)
  File "C:\Users\xyz\AppData\Local\Programs\Python\Python36-32\lib\site-packages\f5\bigip\resource.py", line 941, in _create
    response = session.post(_create_uri, json=kwargs, **requests_params)
  File "C:\Users\xyz\AppData\Local\Programs\Python\Python36-32\lib\site-packages\icontrol\session.py", line 272, in wrapper
    raise iControlUnexpectedHTTPError(error_message, response=response)
icontrol.exceptions.iControlUnexpectedHTTPError: 400 Unexpected Error: Bad Request for uri: https://x.x.x.x:443/mgmt/tm/sys/file/ssl-key/
Text: '{"code":400,"message":"Failed! exit_code (3).\\n","errorStack":[],"apiError":26214401}'

any idea?

 

JRahm
Community Manager
Community Manager

You are missing the file reference (file:) at the beginning of your sourcePath:

key = b.tm.sys.file.ssl_keys.ssl_key.create(name='testkey', sourcePath='file:/var/config/rest/downloads/testcert.key')
cert = b.tm.sys.file.ssl_certs.ssl_cert.create(name='testkey', sourcePath='file:/var/config/rest/downloads/testcert.crt')
gilliek_282631
Altostratus
Altostratus

Hi Jason,

Just found a typo in the URI for downloading an image file in the table at the beginning of the article:

/mgmt/cm/autodeploy/sotfware-image-downloads/*

which must be:

/mgmt/cm/autodeploy/software-image-downloads/*    

(the "f" and "t" letters of the word "software" are inverted).

JRahm
Community Manager
Community Manager

Good catch @gilliek! Fixed...

 

In TMOS v15 the directory to retrieve uploaded files has obviously changed.

You will find uploaded files under /var/config/rest/downloads/tmp/ now.

 

Here are Ansible tasks to upload a certificate, to import it from the temp directory to TMOS filestore and to delete the file from the temp directory afterwards:

 

- name: set certificate path information
  set_fact:
    crt_file_path: "{{ '%s%s_%s_%s.%s' | format(crt_path,crt_prefix,crt_name,crt_suffix,crt_file_extension) }}"
    crt_file_name: "{{ '%s_%s_%s.%s' | format(crt_prefix,crt_name,crt_suffix,crt_file_extension) }}"
    
- name: register cert file properties
  stat:
    path: "{{ crt_file_path }}"
  register: crt_properties
  when: crt_file_path is defined
  
- name: set certificate file size information
  set_fact:
    crt_file_size: "{{ crt_properties.stat.size }}"
  when: crt_properties is defined
  
- name: copy certificate to temp directory
  uri:
    validate_certs: no
    url: https://{{ inventory_hostname }}/mgmt/shared/file-transfer/uploads/{{ crt_file_name }}
    method: POST
    headers:
      Content-Range: "0-{{ crt_file_size | int - 2 }}/{{ crt_file_size }}"
      Content-Type: "application/octet-stream"
      X-F5-Auth-Token: "{{ device_info[inventory_hostname].token }}"
    body: "{{ lookup('file', crt_file_path) }}"
    
- name: copy certificate to TMOS filestore (TMOS v14+)
  uri:
    validate_certs: no
    url: https://{{ inventory_hostname }}/mgmt/tm/sys/crypto/cert
    method: POST
    headers:
      X-F5-Auth-Token: "{{ device_info[inventory_hostname].token }}"
    body_format: json
    body:
      command: install
      name: "{{ '%s_%s_%s' | format(crt_prefix,crt_name,crt_suffix) }}"
      from-local-file: "/var/config/rest/downloads/tmp/{{ '%s_%s_%s.%s' | format(crt_prefix,crt_name,crt_suffix,crt_file_extension) }}"
      
- name: cleanup certificate from temp directory
  uri:
    validate_certs: no
    url: https://{{ inventory_hostname }}/mgmt/tm/util/bash
    method: POST
    headers:
      X-F5-Auth-Token: "{{ device_info[inventory_hostname].token }}"
    body_format: json
    body:
      command: run
      utilCmdArgs: "-c 'rm /var/config/rest/downloads/tmp/{{ '%s_%s_%s.%s' | format(crt_prefix,crt_name,crt_suffix,crt_file_extension) }}'"

You may want to replace the variables in the code snippet above according to your requirements.

Cheers, Stephan

EmmanuelCR
F5 Employee
F5 Employee

Hi, 

If I want to upload/download I guess the only way to do that is with an admin account I tried with a resource-admin account and no matter what I do I always get:

UPLOADING DG FILE......
{
"code": 401,
"message": "Authorization failed: user=https://localhost/mgmt/shared/authz/users/rest resource=/mgmt/shared/file-transfer/uploads/mydg.txt verb=POST uri:http://localhost:8100/mgmt/shared/file-transfer/uploads/mydg.txt referrer:x.x.x.x sender:x.x.x.x",
"referer": "x.x.x.x",
"restOperationId": 7036294,
"kind": ":resterrorresponse"
}

The same happens when I try to download a file.

Is there a way we can do it with a user that is not admin?

Axel_Boersma
Altostratus
Altostratus

Would love to have an answer on this as well, the need for admin rights to upload certs. We have a shared F5 where a customer want's to automate cert updateing. But rights are now set by partition. But they need admin rights for uploading files which is a no go, they are only allowed to edit there own partition. They are resource admin on the F5 for there partition and can import certs without issues via the GUI. Why isn't it possible via the rest-api?

Running latest 15.1.6

AS3 is not an option at this time. And that is also missing alot of features that still doesn't make it very usable. For an other F5 we have to do API call's to BIG-IQ/BIG-IP and AS3 to get things working. As F5 is not willing to fix things, so only AS3 can be used.

JRahm
Community Manager
Community Manager

@EmmanuelCR and @Axel_Boersma, an admin account is required for most iControl REST methods, but there is a workaround where you can assign RBAC to other user accounts. Links to a couple articles that should help pursue that in this post.

 

Roman_Melekh
Altocumulus
Altocumulus

Hello

I went through the forum but did not find any PowerShell-baed implementation on how to download a UCS file. For some reason, I can't translate Perl to PowerShell and even ChatGPT can't 🙂

I am going to try to build something on my own but will be happy to see a 100% working solution. 

Btw, right now I discovered that archive downloaded via GUI has a different size from the /mgmt/shared/file-transfer/ucs-downloads Invoke-WebRequest - 6000 bytes difference. Why? How? Going to dig deeper...

JRahm
Community Manager
Community Manager

Hi @Roman_Melekh, Joel Newton definetly shared a powershell version, but I can't seem to find it. You might drop a comment on his powershell module in the codeshare to see if he's still listening...it's been a while.

https://community.f5.com/t5/codeshare/powershell-module-for-the-f5-ltm-rest-api/ta-p/282980

 

Roman_Melekh
Altocumulus
Altocumulus

I know about this one. It is very old, outdated and does not have the functions that I need. It looks like building my own approach will be simpler. Working on that. 

Version history
Last update:
‎01-Mar-2022 20:22
Updated by: