You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@openwhisk.apache.org by GitBox <gi...@apache.org> on 2018/02/02 16:57:20 UTC

[GitHub] jthomas opened a new issue #12: Swift SDK does not support self-signed server certificates.

jthomas opened a new issue #12: Swift SDK does not support self-signed server certificates.
URL: https://github.com/apache/incubator-openwhisk-runtime-swift/issues/12
 
 
   Local instances of the platform use a self-signed SSL certificate. The Swift SDK (`_Whisk.swift`) fails when invoked against platform endpoints without a valid SSL certificate.
   
   The JavaScript SDK supports a constructor argument to turn off certificat checking to resolve this issue. **Looking into implementing this behaviour for the Swift SDK, I have discovered a blocking issue due to the lack of support in the open-source Swift Foundation libraries.**
   
   In Swift, certificate checking can be turned off by creating a new `URLSessionDelegate` with always trusts the server.
   
   ```swift
   class SessionDelegate:NSObject, URLSessionDelegate
   {
       func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
           let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
           completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
       }
   }
   ```
   
   This can be used when creating the `URLSession` to use based upon a method parameter.
   
   ```swift
   let session = ignoreCerts ? URLSession(configuration: .default, delegate: SessionDelegate(), delegateQueue: nil) : URLSession(configuration: URLSessionConfiguration.default)
   ```
   
   This works on OS X but compiling the code on Linux, I ran into the following issue.
   ```
   _Whisky.swift:25:39: error: incorrect argument label in call (have 'trust:', expected 'coder:')
           let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
                                         ^~~~~~
                                          coder
   root@3a4fde570648:/source# swift -v
   Swift version 4.0 (swift-4.0-RELEASE)
   Target: x86_64-unknown-linux-gnu
   ```
   
   Looking into the source code for the Swift foundation core libraries, I discovered this method has not been implemented.
   https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/URLCredential.swift#L155
   
   ```
   // TODO: We have no implementation for Security.framework primitive types SecIdentity and SecTrust yet
   ```
   
   Talking to the IBM@Swift team, support for this feature is being worked on but won't be available until Swift 5 at the earliest.
   
   Until then, we will have to document the behaviour and wait for the foundation libraries to catch up. I've attached the completed `_Whisk.swift` demonstrating the bug.
   
   ```swift
   /*
    * Licensed to the Apache Software Foundation (ASF) under one or more
    * contributor license agreements.  See the NOTICE file distributed with
    * this work for additional information regarding copyright ownership.
    * The ASF licenses this file to You under the Apache License, Version 2.0
    * (the "License"); you may not use this file except in compliance with
    * the License.  You may obtain a copy of the License at
    *
    *     http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
   
   import Foundation
   import Dispatch
   
   
   class SessionDelegate:NSObject, URLSessionDelegate
   {
       func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
           let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
           completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
       }
   }
   
   class Whisk {
       
       static var baseUrl = ProcessInfo.processInfo.environment["__OW_API_HOST"]
       static var apiKey = ProcessInfo.processInfo.environment["__OW_API_KEY"]
       
       class func invoke(actionNamed action : String, withParameters params : [String:Any], ignoreCerts: Bool = false, blocking: Bool = true) -> [String:Any] {
           let parsedAction = parseQualifiedName(name: action)
           let strBlocking = blocking ? "true" : "false"
           let path = "/api/v1/namespaces/\(parsedAction.namespace)/actions/\(parsedAction.name)?blocking=\(strBlocking)"
           
           return sendWhiskRequestSyncronish(uriPath: path, params: params, method: "POST", ignoreCerts: ignoreCerts)
       }
       
       class func trigger(eventNamed event : String, ignoreCerts: Bool = false, withParameters params : [String:Any]) -> [String:Any] {
           let parsedEvent = parseQualifiedName(name: event)
           let path = "/api/v1/namespaces/\(parsedEvent.namespace)/triggers/\(parsedEvent.name)?blocking=true"
           
           return sendWhiskRequestSyncronish(uriPath: path, params: params, method: "POST", ignoreCerts: ignoreCerts)
       }
       
       class func createTrigger(triggerNamed trigger: String, ignoreCerts: Bool = false, withParameters params : [String:Any]) -> [String:Any] {
           let parsedTrigger = parseQualifiedName(name: trigger)
           let path = "/api/v1/namespaces/\(parsedTrigger.namespace)/triggers/\(parsedTrigger.name)"
           return sendWhiskRequestSyncronish(uriPath: path, params: params, method: "PUT", ignoreCerts: ignoreCerts)
       }
       
       class func createRule(ruleNamed ruleName: String, withTrigger triggerName: String, andAction actionName: String, ignoreCerts: Bool = false) -> [String:Any] {
           let parsedRule = parseQualifiedName(name: ruleName)
           let path = "/api/v1/namespaces/\(parsedRule.namespace)/rules/\(parsedRule.name)"
           let params = ["trigger":triggerName, "action":actionName]
           return sendWhiskRequestSyncronish(uriPath: path, params: params, method: "PUT", ignoreCerts: ignoreCerts)
       }
       
       // handle the GCD dance to make the post async, but then obtain/return
       // the result from this function sync
       private class func sendWhiskRequestSyncronish(uriPath path: String, params : [String:Any], method: String, ignoreCerts: Bool) -> [String:Any] {
           var response : [String:Any]!
           
           let queue = DispatchQueue.global()
           let invokeGroup = DispatchGroup()
           
           invokeGroup.enter()
           queue.async {
               postUrlSession(uriPath: path, ignoreCerts: ignoreCerts, params: params, method: method, group: invokeGroup) { result in
                   response = result
               }
           }
           
           // On one hand, FOREVER seems like an awfully long time...
           // But on the other hand, I think we can rely on the system to kill this
           // if it exceeds a reasonable execution time.
           switch invokeGroup.wait(timeout: DispatchTime.distantFuture) {
           case DispatchTimeoutResult.success:
               break
           case DispatchTimeoutResult.timedOut:
               break
           }
           
           return response
       }
       
       
       /**
        * Using new UrlSession
        */
       private class func postUrlSession(uriPath: String, ignoreCerts: Bool, params : [String:Any], method: String,group: DispatchGroup, callback : @escaping([String:Any]) -> Void) {
           
           guard let encodedPath = uriPath.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else {
               callback(["error": "Error encoding uri path to make openwhisk REST call."])
               return
           }
           
           let urlStr = "\(baseUrl!)\(encodedPath)"
           if let url = URL(string: urlStr) {
               var request = URLRequest(url: url)
               request.httpMethod = method
               
               do {
                   request.addValue("application/json", forHTTPHeaderField: "Content-Type")
                   request.httpBody = try JSONSerialization.data(withJSONObject: params)
                   
                   let loginData: Data = apiKey!.data(using: String.Encoding.utf8, allowLossyConversion: false)!
                   let base64EncodedAuthKey  = loginData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
                   request.addValue("Basic \(base64EncodedAuthKey)", forHTTPHeaderField: "Authorization")
                   let session = ignoreCerts ? URLSession(configuration: .default, delegate: SessionDelegate(), delegateQueue: nil) : URLSession(configuration: URLSessionConfiguration.default)
                   
                   let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in
                       
                       // exit group after we are done
                       defer {
                           group.leave()
                       }
                       
                       if let error = error {
                           callback(["error":error.localizedDescription])
                       } else {
                           
                           if let data = data {
                               do {
                                   //let outputStr  = String(data: data, encoding: String.Encoding.utf8) as String!
                                   //print(outputStr)
                                   let respJson = try JSONSerialization.jsonObject(with: data)
                                   if respJson is [String:Any] {
                                       callback(respJson as! [String:Any])
                                   } else {
                                       callback(["error":" response from server is not a dictionary"])
                                   }
                               } catch {
                                   callback(["error":"Error creating json from response: \(error)"])
                               }
                           }
                       }
                   })
                   
                   task.resume()
               } catch {
                   callback(["error":"Got error creating params body: \(error)"])
               }
           }
       }
       
       // separate an OpenWhisk qualified name (e.g. "/whisk.system/samples/date")
       // into namespace and name components
       private class func parseQualifiedName(name qualifiedName : String) -> (namespace : String, name : String) {
           let defaultNamespace = "_"
           let delimiter = "/"
           
           let segments :[String] = qualifiedName.components(separatedBy: delimiter)
           
           if segments.count > 2 {
               return (segments[1], Array(segments[2..<segments.count]).joined(separator: delimiter))
           } else if segments.count == 2 {
               // case "/action" or "package/action"
               let name = qualifiedName.hasPrefix(delimiter) ? segments[1] : segments.joined(separator: delimiter)
               return (defaultNamespace, name)
           } else {
               return (defaultNamespace, segments[0])
           }
       }
   }
   ```
   

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services