aws-amplify / aws-sdk-ios

AWS SDK for iOS. For more information, see our web site:
https://aws-amplify.github.io/docs
Other
1.68k stars 885 forks source link

ResourceNotFoundException #4160

Closed RoustamManookian closed 2 years ago

RoustamManookian commented 2 years ago

Describe the bug

I use AWS Amplify for signUp and sign In When the user signs up for the first time the AWS console creates an identity provider for the new user. Then I get the identity Id and try to attach it to IOT Core policy in order to be able to use IOT Core.

The IOT.attachPolicy() function is working perfectly on ANDROID and in CLI but in IOS I get ResourceNotFoundException !!!

func attachPolicy(identityId: String){

        guard let attachPolicyRequest = AWSIoTAttachPolicyRequest()
        else{
            return print("Error getting AWSIoTAttachPrincipalPolicyRequest")
        }
        attachPolicyRequest.policyName = AWS_IOT_POLICY
        attachPolicyRequest.target = identityId

        guard let iot = MQTTManager.manager.iot else {
            return print("Error getting iot")
        }

        guard let policy = AWSIoTGetPolicyRequest() else{return}
        policy.policyName = AWS_IOT_POLICY

        iot.attachPolicy(attachPolicyRequest, completionHandler: { error in
            if let error = error{
                print("Failed to attach Policy. ", error)
            }else{
                print("ATTACHED POLICY Successfully !!!")
            }
        })
    }

and this is the output error Failed to attach Policy. Error Domain=com.amazonaws.AWSIoTErrorDomain Code=20 "Not Found" UserInfo={NSLocalizedDescription=Not Found, NSLocalizedFailureReason=ResourceNotFoundException:}

Steps To Reproduce

Steps to reproduce the behavior:
1. new user signs up
2. AWS Console adds an identity provider for the new user in identity pool
3. The function takes the newly created identity id and tries to attach it to IOT Core Policy
4. And get the ResourceNotFoundException error

Expected behavior

After the user has signed up for the first time iot.attachPolicy function has to attach the newly created identity id to iot.policy

I have to mention that this action is working perfectly on ANDROID and in CLI

Amplify Framework Version

1.21.1

Amplify Categories

Auth

Dependency manager

Swift PM

Swift version

5

CLI version

7.6.12

Xcode version

13.2.1

Relevant log output

No response

Is this a regression?

No

Regression additional context

No response

Device

iPhoneX

iOS Version

14

Specific to simulators

No response

Additional context

No response

EdXian commented 2 years ago

Hi @diegocstn , I have encountered a similar problem. I replaced the function attachPolicy with attachprincipalPolicy then I got an another error:

connecting ...     (status code 1)
connecting error  (status code 5)
connecting ...     (status code 1)
connecting error  (status code 5)

The issue is similar to this . So far , any idea?

thisisabhash commented 2 years ago

Hello, Could you please check out our IoT Sample App [https://github.com/awslabs/aws-sdk-ios-samples/tree/main/IoT-Sample/Swift] to refer to how to AWS IoT APIs in your applications? Please also refer to : https://docs.amplify.aws/sdk/pubsub/getting-started/q/platform/ios/#aws-iot on how to attach IoT policy to your Cognito Identity.

RoustamManookian commented 2 years ago

Hi in your sample you use attachPrincipalPolicy() function but in documentation, it's written that this function is deprecated and suggested to use attachPolicy() function instead. Greenshot 2022-03-07 07 19 49

EdXian commented 2 years ago

Hi @RoustamManookian @thisisabhash , Thank you for your help! I have replaced the function attachPolicy() with attachPrinciplePolicy(). However, the issue still exists. I try another way to workaround. The following code shows the snippets.

  1. check the default user whether he has certificateId , if not , then the mobile automatically creates a new certificate for him.
  2. if the default user has a certificateId then attach the specific policy to his identity ID.
  3. After getting the certificateId, the mobile connects to the iot core.
                    if let certificateId = defaults.string(forKey: "certificateId") {
                        print("cerid == \(certificateId)")
                        self.attachIdPolicy()
                        self.connectDataManager(cerid: certificateId)

                    } else {

                        self.createCertificateIdAndStoreinNSUserDefaults { cerid in
                            print("cerid \(cerid)")
                            defaults.setValue(cerid, forKey: "certificateId")
                            self.connectDataManager(cerid: cerid)
                        } onFailure: { error in
                            print("is error")
                        }
                    }
    func  attachIdPolicy() {

        let id = AWSMobileClient.default().getIdentityId()
        let attachPrincipalPolicyRequest = AWSIoTAttachPrincipalPolicyRequest()
        attachPrincipalPolicyRequest?.policyName = POLICY_NAME
        attachPrincipalPolicyRequest?.principal = id.result as String?

        IotManager.shared.iot.attachPrincipalPolicy(attachPrincipalPolicyRequest!).continueWith { task in

            print("attach id policy finish, erro ->>>>  \(task.error)")
        }
    }
  private func createCertificateIdAndStoreinNSUserDefaults(onSuccess:  @escaping (String)->Void, onFailure: @escaping (Error) -> Void) {
        let defaults = UserDefaults.standard

        let csrDictionary = [ "commonName": CertificateSigningRequestCommonName,
                              "countryName": CertificateSigningRequestCountryName,
                              "organizationName": CertificateSigningRequestOrganizationName,
                              "organizationalUnitName": CertificateSigningRequestOrganizationalUnitName]

        IotManager.shared.iotManager.createKeysAndCertificate(fromCsr: csrDictionary) { (response) -> Void in
            guard let response = response else {

                onFailure(NSError(domain: "No response on iotManager.createKeysAndCertificate", code: -2, userInfo: nil))
                return
            }

            defaults.set(response.certificateId, forKey:"certificateId")

            let certificateId = response.certificateId
            let attachPrincipalPolicyRequest = AWSIoTAttachPrincipalPolicyRequest()
            attachPrincipalPolicyRequest?.policyName = POLICY_NAME
            attachPrincipalPolicyRequest?.principal = response.certificateArn

            IotManager.shared.iot.attachPrincipalPolicy(attachPrincipalPolicyRequest!).continueWith { task in

                if let certificateId = certificateId {
                    onSuccess(certificateId)
                } else {
                    onFailure(NSError(domain: "Unable to generate certificate id", code: -1, userInfo: nil))
                }
                return nil
            }

I also attached the following policy to authenticated users in Cognito Identity Pool.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*",
                "cognito-identity:*"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": "iot:Connect",
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "iot:Subscribe",
                "iot:Receive",
                "iot:Publish"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "iot:AttachPrincipalPolicy",
                "iot:CreateKeysAndCertificate",
                "iot:CreateCertificateFromCsr"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}

Please let me know, If you have better solutions .

RoustamManookian commented 2 years ago

Hi, Why do I need to use "createKeysAndCertificate" if I authenticate with Amplify ??

and by the way, I granted iotFullAccess to authenticated users in Cognito Identity Pool.

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iot:*" ], "Resource": "*" } ] }

EdXian commented 2 years ago

hi @RoustamManookian I find that the function iotDataManager.connect always need an argument called certificateId.

As you can see the following snippets.

    private func connectDataManager(cerid:String) {

        let uuid = UUID().uuidString
        IotManager.shared.iotDataManager.connect( withClientId: uuid,
                                                  cleanSession:true,
                                                  certificateId:cerid,
                                                  statusCallback: self.mqttEventCallback)
        delegate?.loginFinish()
    }

So I think I should give a temporary certificate for the function berfore connect to iot core.

I have tried another example. Maybe you can take a look at this example

Then you can use connectUsingWebSocket to connect aws iot core.

    private func mqttConnect() {
        print("Connecting to MQTT ...")
        self.iotDataManager.connectUsingWebSocket(withClientId: self.clientId, cleanSession: true, statusCallback: mqttEventCallback(_:))
    }
RoustamManookian commented 2 years ago

But I don't have any problem with iotDataManager connection. I use "guard let userName = Amplify.Auth.getCurrentUser()?.username" and try to connect with current User userName which is the userId.

func connectToMQTT(connectionCallBack: @escaping (MQTT_Status)->Void){

        Amplify.Auth.fetchAuthSession { [weak self] result in
            switch result {
            case .success:

                guard let userName = Amplify.Auth.getCurrentUser()?.username
                else{
                    print("OnSuccess: Problem getting userID")
                    return
                }

                guard let dataManager = self?.iotDataManager,
                      let status = self?.connectionStatus
                else{
                    print("OnSuccess: Problem getting dataManager")
                    return
                }

                if status.status == .disconnected || status.status == .unknown{
                    if(!dataManager.connectUsingWebSocket(withClientId: userName,cleanSession: true,
                                                          statusCallback: {status in
                        self?.mqttEventCallback(status, connectionCallBack: connectionCallBack)
                    }))
                    {
                        print("ERROR Trying to connect. Connection Status: \(status)")
                    }
                }
                else{
                    print("Connection Status: \(status)")
                }
            case .failure(let error):
                print(error)
            }

        }
    }
EdXian commented 2 years ago

Hi @RoustamManookian ,

I think the argument withClientId should be given as a user's identity from credentialProvider rather than a user's name. An identity is something like ap-southeast-1:d4512355-814d-4349-aa83-xxxxxxxxxxxx.

RoustamManookian commented 2 years ago

I just created an API that triggers a lambda function which makes the attachPolicy() in JavaScript

RoustamManookian commented 2 years ago

var AWS = require('aws-sdk');
var iot = new AWS.Iot({apiVersion: '2015-05-28'});

exports.handler = async(event,context)=>{

    var identity_id = "";
    var userName = "";

    if(event.queryStringParameters != null){
        if (event.queryStringParameters.hasOwnProperty("identity_id")){
            identity_id = event.queryStringParameters.identity_id;
        }
        else{
            console.log("*** My Logs: line 32 -> identityId is empty");
        }
        if (event.queryStringParameters.hasOwnProperty("userName")){
            userName = event.queryStringParameters.userName;
        }
        else{
            console.log("*** My Logs: line 38 -> userName is empty");
        }
    }
    console.log("userName: -> *" + userName + "*");
    console.log("identityId: -> *" + identity_id + "*");

    if(identity_id != "" && userName != ""){

    //**************************************************************************
    //  Attach Policy
    //**************************************************************************
        const attachPolicyResult = new Promise(function(resolve, reject){

            let params = {
                policyName: 'iot_full_access_policy',
                target: identity_id
                };

            iot.attachPolicy(params, function(err, data) {
                if (err){
                    console.log("*** ERROR: line 54 -> " + err, err.stack);
                    reject('ERROR: ', err);
                }
                resolve(JSON.stringify(data));
            });
        });

        var str_result = (await attachPolicyResult);
brennanMKE commented 2 years ago

@RoustamManookian Can you check if you have set your endpoint in awsconfiguration.json? It should have the details below with your unique endpoint for IoT.

"IoT": {
    "Default": {
        "Region": "us-east-1",
        "Endpoint": "https://change_me-ats.iot.us-east-1.amazonaws.com/"
    }
},
"IoTManager": {
    "Default": {
        "Region": "us-east-1",
        "Endpoint": "https://change_me-ats.iot.us-east-1.amazonaws.com/"
    }
},

When you get the "Resource Not Found" exception it could be due to using the wrong endpoint.

Access Control

I would also change your policy so that it is more restrictive.

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iot:*" ], "Resource": "*" } ] }

The policy you shared earlier allows for any IoT action when you could limit it to just what you really need for this client. You can see the list of actions below.